跳到正文

← 全部工具

给 iOS 和 Mac 开发者的免费工具

JSON 转 Swift Codable

粘贴一段 JSON 响应,得到能用 JSONDecoder 解码它的 Swift 类型:每个对象一个 Codable struct(或 final class),嵌套对象成为单独的类型并用单数命名,在某些示例里缺失或为 null 的属性写成可选,还附上配套的解码代码。示例无法确定的地方会明确标出,而不是乱猜。你的 JSON 只留在本页。

是合法 Swift 名称的键原样使用(user_name 仍是 user_name)。像 "my-key" 这样的键会得到驼峰名称和一条 CodingKeys。

字母、数字和下划线,其他字符会被清理掉。

关闭时,这些属性写成 String? 并带 // TODO 注释,由你决定类型。打开时,会生成一个小的 JSONValue 枚举,可以解码任何 JSON 值。

声明数
3
输入大小
345 B

示例无法确定这些类型

  • Root.manager 在示例里全是 null

代码里每一处都标了 // TODO。String? 属性只能解码文本或 null,在你选定类型(或打开 JSONValue 选项)之前,遇到其他值解码会失败。

Swift 类型

import Foundation

struct Root: Codable, Identifiable {
    let id: Int
    let user_name: String
    let is_active: Bool
    let score: Double
    let created_at: Date
    let `default`: String
    let manager: String? // TODO: only null in the sample; set the real type
    let tags: [String]
    let orders: [Order]
    let address: Address
}

struct Order: Codable, Identifiable {
    let id: String
    let total: Double
    let coupon: String?
}

struct Address: Codable {
    let street: String
    let zip: String
}

解码

放在你解码的地方。data 是收到的 Data,例如来自 URLSession。

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let value = try decoder.decode(Root.self, from: data)

它是怎么工作的

  • •用严格的解析器读取 JSON,并保留每个数字的原始写法,所以 1 是 Int,1.0 是 Double。某个字段一处是 1、另一处是 2.5,就写成 Double。
  • •对象数组里的每个元素会合并成一个类型。某个键在部分元素里缺失,或在任何一个元素里为 null,属性就是可选的。
  • •嵌套对象成为单独的类型,按键名的单数形式命名:items 变成 Item,categories 变成 Category。会遮住 Swift 或 Foundation 类型的名字(如 Data、Date)会在前面加上父类型名(RootData)。
  • •不是合法 Swift 名称的键会得到驼峰命名的属性和一条 CodingKeys。default、class、self 等 Swift 关键字用反引号包起来。
  • •ISO 8601 日期时间格式的字符串会成为 Date,解码代码会把 dateDecodingStrategy 设为 .iso8601;如果日期带小数秒(.iso8601 读不了),则给出一个简短的自定义解码策略。
  • •对象有普通的 id 字段时加上 Identifiable。class 是 final 的,并且会写出 == 和 hash(into:),因为 Swift 只会为 struct 和 enum 提供它们。

它做不到的事

  • •它只知道你的示例里有什么。真实响应里有时缺失、但在这里每个示例都出现的字段不会被写成可选;把几个示例放进一个数组里粘贴,就能覆盖到。
  • •它不会把字符串值变成枚举。如果 status 总是 "open" 或 "closed",你也许想要一个 String 原始值的枚举,这要由你决定。
  • •不是 ISO 8601 文本的日期,例如 Unix 时间戳或 "2026-03-07",会保留为 Int、Double 或 String。请用 .secondsSince1970 或 DateFormatter 处理。
  • •不同键下结构相同的对象会生成不同的类型;如果它们其实是同一个东西,请手动合并。
  • •它不生成 SwiftData 的 @Model 类,也不写自定义的 init(from:)。
  • •属性名只用 ASCII 字符。完全由其他字符(例如中文)组成的键会得到 field 之类的名字,并用 CodingKeys 对应真实的键。

常见问题

该用 CodingKeys 还是 keyDecodingStrategy = .convertFromSnakeCase?
整个接口都统一用 snake_case 时用 .convertFromSnakeCase:类型更简洁,由解码器负责对应。键的风格不统一、含有连字符等字符,或者你希望无论用哪个解码器对应关系都成立时,用 CodingKeys。注意这个策略会把 user_ID 变成 userId,把 api_URL 变成 apiUrl,属性名必须与之完全一致。
我的 JSON 里明明有值,为什么属性是可选的?
因为在数组的至少一个元素里,它缺失或为 null。非可选的键缺失时 JSONDecoder 会抛出 keyNotFound,遇到 null 会抛出 valueNotFound,所以必须写成可选,每个元素才能解码成功。
为什么数字是 Double 而不是 Int?
因为它在某处带了小数点或指数(1.0、2.5、1e3),或者超出了 Int 的范围。JSONDecoder 不会把 2.5 解码成 Int,但可以把 2 解码成 Double,所以 Double 才能适用于见到的所有值。
我的日期带毫秒,.iso8601 解码失败,为什么?
JSONDecoder 的 .iso8601 策略不接受 2026-03-07T17:50:56.123Z 这样的小数秒。示例里有小数秒时,解码代码会改用带 ISO8601DateFormatter 和 .withFractionalSeconds 的自定义策略,并在失败时再按整秒解析。
代码里的 TODO 注释是什么意思?
示例在那里看不出可用的类型:值总是 null、数组为空,或者字段有不同类型(一处是数字,另一处是文本)。属性写成 String? 让代码能编译,但它只能解码文本或 null。请选定真实类型,或打开 JSONValue 选项,得到一个能解码任何值的枚举。
用 struct 还是 class?
解码出的数据通常用 struct:它是值类型,属性满足条件时就是 Sendable,Swift 也会替你写好 Equatable 和 Hashable。需要引用语义时选 final class,例如在多个视图间共享同一个解码后的对象并就地修改。

相关工具