2

我的数据如下所示:

"places": [
        {
            "id": 15,
            "name": "København",
            "typeId": 6,
            "coordinates": {
                "lat": "55.6760968",
                "lng": "12.5683372"
            },
            "count": 2779
        },
        {
            "id": 19,
            "name": "København S",
            "typeId": 3,
            "coordinates": {
                "lat": "55.6508754",
                "lng": "12.5991891"
            },
            "count": 1168
        }
]

我希望避免这种情况:

struct Places: Decodable {
    let places: [Place]
}

QuickType.io 建议: https ://app.quicktype.io?share=j22hopuBnkuHZziOSvxG 而只是在“地点”列表中解码。这样就可以了:

let places = try JSONDecoder().decode([Place].self, from: data)

到目前为止我发现的可能解决方案:

  1. “磨损”解决方案: https ://stackoverflow.com/a/62403633/13481876

  2. 创建通用可解码数组结构: https ://swiftsenpai.com/swift/decode-dynamic-keys-json/

4

2 回答 2

1

Places有一种可能的方法可以通过将 JSON 解码为[String: [Place]]type 然后获取 Dictionaryvalues属性的第一个元素来避免顶级结构 ( ):

let decoder = JSONDecoder()
do {
    let places = try decoder.decode([String: [Place]].self, from: data)
    print(places.values.first ?? [])
} catch {
    print(error)
}
于 2020-11-27T10:44:33.703 回答
1

如果您发现自己多次需要这个,那么您可以构建自己的通用结构来解码它找到的任何键:

struct Nester<T: Decodable>: Decodable {
    let elements: [T]
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let key = container.allKeys.first {
            elements = try container.decode([T].self, forKey: key)
        } else {
            // we run into an empty dictionary, let's signal this
            throw DecodingError.typeMismatch([String:Any].self, DecodingError.Context(codingPath: [], debugDescription: "Expected to find at least one key"))
        }
    }
    
    // A coding key that accepts whatever string value it is given
    struct CodingKeys: CodingKey {
        let stringValue: String
        var intValue: Int? { nil }
        
        init?(stringValue: String) {
            self.stringValue = stringValue
        }
                
        init?(intValue: Int) { return nil }
    }
}

有了这个,您可以扩展JSONDecoder以获得更好的呼叫站点:

extension JSONDecoder {
    func decode<T: Decodable>(nested: [T].Type, from data: Data) throws -> [T] {
        try decode(Nester<T>.self, from: data).elements
    }
}

然后只需调用新的重载即可:

let places = try JSONDecoder().decode(nested: [Place].self, from: data)

PS 如果你愿意,你可以在扩展中隐藏复杂的结构,结果是这样的:

extension JSONDecoder {
    func decode<T: Decodable>(nested: [T].Type, from data: Data) throws -> [T] {
        try decode(Nester<T>.self, from: data).elements
    }
    
    private struct Nester<T: Decodable>: Decodable {
        let elements: [T]
        
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            if let key = container.allKeys.first {
                elements = try container.decode([T].self, forKey: key)
            } else {
                throw DecodingError.typeMismatch([String:Any].self, DecodingError.Context(codingPath: [], debugDescription: "Expected to find at least one key"))
            }
        }
        
        struct CodingKeys: CodingKey {
            let stringValue: String
            var intValue: Int? { nil }
            
            init?(stringValue: String) {
                self.stringValue = stringValue
            }
                    
            init?(intValue: Int) { return nil }
        }
    }
}

不利的一面是,如果您想扩展除 JSON 之外的其他解码器,您将无法重用该结构。

于 2020-11-27T01:30:11.017 回答