2

My data looks like this:

"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
        }
]

I wish to avoid this:

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

Which is suggested by QuickType.io: https://app.quicktype.io?share=j22hopuBnkuHZziOSvxG And instead just decode within "places" list. Such that this would work:

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

Possible solutions that I've found so far:

  1. The 'Scuffed' solution: https://stackoverflow.com/a/62403633/13481876

  2. Create generic decodable array struct: https://swiftsenpai.com/swift/decode-dynamic-keys-json/

  • 1
    No you can't. You need to start from the top so to speak. But why is this a problem, you can easily access and keep only the Place array? – Joakim Danielson Nov 26 '20 at 20:39
  • @JoakimDanielson I just find it ugly, to have a struct, only to properly decode my json. I feared that this was the case. – Alexander Flensborg Nov 26 '20 at 21:57
  • `let places = try JSONDecoder().decode(Places.self, from: data).places` – Leo Dabus Nov 27 '20 at 03:26
  • I started by using Cristik's answer, however as I continued to implement the rest of my webRepo for the API, I found the above mentioned case, to be an edge case, and not applicable to anything but that one struct/API call. I therefore ended up using the method that gcharita mentioned, which decodes my object to [String: [OBJECT] ] Overall I think Cristik's method is more elegant, and is the solution I would've used, had I had the need for it, more than a few occurrences. Since my mindset was on a less cluttered namespace, I went for gcharita's. – Alexander Flensborg Dec 10 '20 at 20:59

2 Answers2

2

If you find yourself needing this multiple times, then you can build your own generic struct that decodes over whichever key it finds:

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 }
    }
}

And with this in hand, you can extend JSONDecoder in order to get a nicer call site:

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

Then it's just a matter of calling the new overload:

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

P.S. if you want, you can hide the complex struct within the extension, resulting in something like this:

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 }
        }
    }
}

The downside is that you'll not be able to reuse the struct if you want to extend other decoders besides the JSON one.

Cristik
  • 30,989
  • 25
  • 91
  • 127
  • The idea regarding a `JSONDecoder` extension is good for reusability. Combining the 2 answer will have an interesting result :) – gcharita Nov 27 '20 at 11:35
1

There is a possible way to avoid the top level struct (Places) by decoding your JSON to [String: [Place]] type and then get the first element of the Dictionary values property:

let decoder = JSONDecoder()
do {
    let places = try decoder.decode([String: [Place]].self, from: data)
    print(places.values.first ?? [])
} catch {
    print(error)
}
gcharita
  • 7,729
  • 3
  • 20
  • 37