You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift使用JSONDecoder将嵌套JSON解析为字符串存入Codable结构体的方法

问题原因

你当前解析失败的核心原因是:JSON数据中的properties字段为JSON对象类型,而你在AnimalModel中将其声明为String类型,JSONDecoder默认无法直接将JSON对象映射为Swift字符串,触发类型不匹配错误。

实现方案

你需要自定义Decodable的解码逻辑,先将properties对应的JSON对象解码为字典/通用JSON结构,再重新序列化为字符串存储。如果还需要支持将Model编码回JSON,也需要同步自定义Encodable逻辑。

完整代码实现
import Foundation

struct AnimalModel: Identifiable, Codable {
    let id: String
    let type: String
    let properties: String // 存储properties对应的JSON字符串
    
    enum CodingKeys: String, CodingKey {
        case id, type, properties
    }
    
    // 自定义解码逻辑
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        // 固定字段正常解码
        id = try container.decode(String.self, forKey: .id)
        type = try container.decode(String.self, forKey: .type)
        
        // 解码properties为任意JSON对象
        let propertiesObj = try container.decode([String: AnyCodable].self, forKey: .properties)
        // 重新序列化为JSON数据再转字符串
        let jsonData = try JSONEncoder().encode(propertiesObj)
        guard let propertiesStr = String(data: jsonData, encoding: .utf8) else {
            throw DecodingError.dataCorruptedError(
                forKey: .properties,
                in: container,
                debugDescription: "properties字段无法转换为UTF8字符串"
            )
        }
        properties = propertiesStr
    }
    
    // 若不需要将Model转回JSON可省略该方法
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(type, forKey: .type)
        // 将properties字符串转回JSON对象再编码
        guard let jsonData = properties.data(using: .utf8),
              let propertiesObj = try JSONDecoder().decode([String: AnyCodable].self, from: jsonData) else {
            throw EncodingError.invalidValue(
                properties,
                .init(codingPath: [CodingKeys.properties], debugDescription: "properties字符串不是合法JSON")
            )
        }
        try container.encode(propertiesObj, forKey: .properties)
    }
}

// 通用解码辅助类型,支持解析任意类型的JSON值
fileprivate struct AnyCodable: Codable {
    let value: Any
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let boolVal = try? container.decode(Bool.self) {
            value = boolVal
        } else if let intVal = try? container.decode(Int.self) {
            value = intVal
        } else if let doubleVal = try? container.decode(Double.self) {
            value = doubleVal
        } else if let stringVal = try? container.decode(String.self) {
            value = stringVal
        } else if let arrayVal = try? container.decode([AnyCodable].self) {
            value = arrayVal.map { $0.value }
        } else if let dictVal = try? container.decode([String: AnyCodable].self) {
            value = dictVal.mapValues { $0.value }
        } else if container.decodeNil() {
            value = Optional<Any>.none as Any
        } else {
            throw DecodingError.dataCorruptedError(in: container, debugDescription: "不支持的JSON类型")
        }
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch value {
        case let boolVal as Bool:
            try container.encode(boolVal)
        case let intVal as Int:
            try container.encode(intVal)
        case let doubleVal as Double:
            try container.encode(doubleVal)
        case let stringVal as String:
            try container.encode(stringVal)
        case let arrayVal as [Any]:
            try container.encode(arrayVal.map { AnyCodable(value: $0) })
        case let dictVal as [String: Any]:
            try container.encode(dictVal.mapValues { AnyCodable(value: $0) })
        case is Optional<Any>:
            try container.encodeNil()
        default:
            throw EncodingError.invalidValue(value, .init(codingPath: encoder.codingPath, debugDescription: "不支持的Swift类型"))
        }
    }
}
注意事项
  1. 你提供的示例JSON存在语法错误:第二个fish对象的properties中"num_teeth": "3"后缺少逗号,需先修正JSON语法再解析。
  2. 如果不需要保留JSON的格式化排版,序列化时可以去掉格式化参数,生成压缩后的JSON字符串节省空间。
  3. 如果你确定所有properties的值都是字符串类型,可以去掉AnyCodable的实现,直接将properties解码为[String: String]类型,再用JSONSerialization转字符串即可,代码会更简洁。

内容的提问来源于stack exchange,提问作者user2926577

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 00:48:00