Example
[
{
"id": 42,
"full_name": "Ada Lovelace",
"avatar_url": "https://example.com/ada.png",
"joined_at": "2026-03-14T09:26:53Z",
"roles": [
"admin"
]
},
{
"id": 43,
"full_name": "Grace Hopper",
"avatar_url": null,
"joined_at": "2026-04-01T12:00:00Z",
"roles": [],
"team": {
"id": 7,
"name": "Compilers"
}
}
]
import Foundation
// The JSON root is an array: decode [UserItem].self
// Dates are ISO 8601: set decoder.dateDecodingStrategy = .iso8601Flexible (below; the built-in .iso8601 rejects fractional seconds)
struct UserItem: Codable {
let id: Int
let fullName: String
let avatarUrl: URL?
let joinedAt: Date
let roles: [String]
let team: Team?
enum CodingKeys: String, CodingKey {
case id
case fullName = "full_name"
case avatarUrl = "avatar_url"
case joinedAt = "joined_at"
case roles
case team
}
}
struct Team: Codable {
let id: Int
let name: String
}
extension JSONDecoder.DateDecodingStrategy {
/// ISO 8601 date-times with or without seconds and fractional seconds; .iso8601 only reads whole seconds.
static var iso8601Flexible: Self {
.custom { decoder in
let text = try decoder.singleValueContainer().decode(String.self)
// "2026-01-01T09:00Z" has no seconds, which ISO8601DateFormatter requires.
let full = text.replacingOccurrences(of: #"T(\d{2}:\d{2})(?=Z|[+-])"#, with: "T$1:00", options: .regularExpression)
let f = ISO8601DateFormatter()
for options: ISO8601DateFormatter.Options in [[.withInternetDateTime, .withFractionalSeconds], [.withInternetDateTime]] {
f.formatOptions = options
if let date = f.date(from: full) { return date }
}
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Not an ISO 8601 date-time: \(text)"))
}
}
}
full_namebecamefullName, mapped back inCodingKeys.avatar_urlis null for one user, so it is optional. It looks like a URL, so it isURL?.teamonly appears for one user, so it isTeam?and may be missing from the JSON.joined_atis an ISO 8601 timestamp, so it is aDate.
Decoding with JSONDecoder
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let user = try decoder.decode(User.self, from: data)
print(user.fullName)
// An array at the root decodes the same way
let users = try decoder.decode([User].self, from: data)
let encoded = try JSONEncoder().encode(user)
Type mapping
| JSON | Swift |
|---|---|
| string | String (or Date, URL, UUID) |
| integer | Int (64-bit), UInt64 or Decimal for larger values |
| decimal number | Double |
| true / false | Bool |
| object | struct … : Codable |
| array | [T] |
| sometimes missing or null | T? |
| mixed types | JSONValue |
Building for Android too? Generate Kotlin data classes or Dart classes for Flutter from the same JSON.
Frequently asked questions
How do I convert JSON to a Swift struct?
Paste a JSON sample into the tool above. It creates a Codable struct for every object, converts snake_case keys to camelCase properties with a CodingKeys enum for the original names, and makes values that are null or missing in some samples optional.
Do I need CodingKeys, or can I use convertFromSnakeCase?
Either works for plain snake_case keys. Setting decoder.keyDecodingStrategy = .convertFromSnakeCase removes the need for CodingKeys, but it also changes keys you didn’t expect (user_id becomes userId, not userID) and applies to every type. CodingKeys are explicit and keep working whatever decoder the struct is used with.
Why does decoding fail with keyNotFound?
A non-optional property is missing from the JSON. Paste several samples as an array so the converter can see which keys are sometimes absent; it makes those properties optional, and Codable then accepts a missing key as nil.
How are dates decoded?
With Date, URL and UUID types ticked, ISO 8601 strings become Date. JSONDecoder doesn’t read ISO 8601 by default, so set decoder.dateDecodingStrategy = .iso8601 (for fractional seconds, use a custom ISO8601DateFormatter with .withFractionalSeconds). Leave the box unticked to keep dates as String.
What is the JSONValue enum for?
Swift has no Codable “any” type. When a field holds different types in different samples, for example a number in one and a string in another, the converter adds a small JSONValue enum that can decode and encode any JSON value.