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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 08:12:05