Swift中如何解析包含嵌套GeoJSON的WebService响应?
解决Swift中解析带分页信息的GeoJSON响应问题
问题背景
调用WebService返回的响应包含分页元数据(pageSize/pageNum等)和GeoJSON格式的items数组,但MKGeoJSONObject未遵循Codable协议,直接定义包含[MKGeoJSONObject]的Codable结构体解码会失败,手动截取JSON内容的方法不够优雅,需要合规方案。
方案1:拆分解码——先拿分页信息,再解析GeoJSON
先解码分页元数据,同时提取items对应的原始JSON数据,再用MKGeoJSONDecoder单独解析items:
// 定义只包含分页字段的结构体,同时保留items的原始JSON数据 struct PartialWebResponse: Codable { let pageSize: Int let pageNum: Int let totalCount: Int let totalPages: Int let items: [JSON] // 自定义JSON类型,用于保留原始JSON结构 } // 自定义JSON类型,用于承载未解码的JSON数据 struct JSON: Codable { let rawValue: Any init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() rawValue = try container.decode(AnyCodable.self).value } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(AnyCodable(rawValue)) } } // 辅助类型,用于解码Any类型 struct AnyCodable: Codable { let value: Any init(_ value: Any) { self.value = value } init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let intValue = try? container.decode(Int.self) { value = intValue } else if let stringValue = try? container.decode(String.self) { value = stringValue } else if let doubleValue = try? container.decode(Double.self) { value = doubleValue } else if let boolValue = try? container.decode(Bool.self) { value = boolValue } else if let arrayValue = try? container.decode([AnyCodable].self) { value = arrayValue.map { $0.value } } else if let dictValue = try? container.decode([String: AnyCodable].self) { value = dictValue.mapValues { $0.value } } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unsupported JSON type") } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch value { case let int as Int: try container.encode(int) case let string as String: try container.encode(string) case let double as Double: try container.encode(double) case let bool as Bool: try container.encode(bool) case let array as [Any]: try container.encode(array.map(AnyCodable.init)) case let dict as [String: Any]: try container.encode(dict.mapValues(AnyCodable.init)) default: throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "Unsupported JSON type")) } } } // 实际解码逻辑 func decodeFullResponse(data: Data) throws -> (metadata: PartialWebResponse, features: [MKGeoJSONObject]) { // 解码分页信息和原始items数据 let partialResponse = try JSONDecoder().decode(PartialWebResponse.self, from: data) // 将原始items数据转为Data,再用MKGeoJSONDecoder解析 let itemsData = try JSONEncoder().encode(partialResponse.items) let features = try MKGeoJSONDecoder().decode(itemsData) as? [MKGeoJSONObject] ?? [] return (partialResponse, features) }
方案2:自定义WebServiceResponse的解码实现
给WebServiceResponse编写自定义的init(from:)方法,手动处理每个字段的解码,对items单独用MKGeoJSONDecoder解析:
struct WebServiceResponse { let pageSize: Int let pageNum: Int let totalCount: Int let totalPages: Int let items: [MKGeoJSONObject] } extension WebServiceResponse: Decodable { init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // 解码分页元数据 pageSize = try container.decode(Int.self, forKey: .pageSize) pageNum = try container.decode(Int.self, forKey: .pageNum) totalCount = try container.decode(Int.self, forKey: .totalCount) totalPages = try container.decode(Int.self, forKey: .totalPages) // 提取items的JSON数组,转为Data后用MKGeoJSONDecoder解析 let itemsArray = try container.decode([AnyCodable].self, forKey: .items) let itemsData = try JSONEncoder().encode(itemsArray) items = try MKGeoJSONDecoder().decode(itemsData) as? [MKGeoJSONObject] ?? [] } enum CodingKeys: String, CodingKey { case pageSize, pageNum, totalCount, totalPages, items } } // 同样需要用到方案1中的AnyCodable辅助类型 // 使用方式 let response = try JSONDecoder().decode(WebServiceResponse.self, from: data)
方案3:用JSONSerialization拆分处理
直接将整个响应转为JSON对象,提取分页字段后,把items数组转为Data再解析:
func decodeResponse(data: Data) throws -> (pageSize: Int, pageNum: Int, totalCount: Int, totalPages: Int, items: [MKGeoJSONObject]) { guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { throw NSError(domain: "DecodeError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid JSON format"]) } // 提取分页信息 let pageSize = json["pageSize"] as? Int ?? 0 let pageNum = json["pageNum"] as? Int ?? 0 let totalCount = json["totalCount"] as? Int ?? 0 let totalPages = json["totalPages"] as? Int ?? 0 // 提取items数组并转为Data guard let itemsArray = json["items"] as? [Any] else { throw NSError(domain: "DecodeError", code: -2, userInfo: [NSLocalizedDescriptionKey: "Missing or invalid items array"]) } let itemsData = try JSONSerialization.data(withJSONObject: itemsArray, options: []) // 解析GeoJSON let items = try MKGeoJSONDecoder().decode(itemsData) as? [MKGeoJSONObject] ?? [] return (pageSize, pageNum, totalCount, totalPages, items) }
内容的提问来源于stack exchange,提问作者Wouter
相关产品推荐
相关产品推荐

