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类型")) } } }
注意事项
- 你提供的示例JSON存在语法错误:第二个fish对象的
properties中"num_teeth": "3"后缺少逗号,需先修正JSON语法再解析。 - 如果不需要保留JSON的格式化排版,序列化时可以去掉格式化参数,生成压缩后的JSON字符串节省空间。
- 如果你确定所有
properties的值都是字符串类型,可以去掉AnyCodable的实现,直接将properties解码为[String: String]类型,再用JSONSerialization转字符串即可,代码会更简洁。
内容的提问来源于stack exchange,提问作者user2926577
相关产品推荐
相关产品推荐

