1

I have this json

{
    "status": [
        {
            "state": "checked",
            "errorCode": "123",
            "userId": "123456"
        }
    ]
}

this is an array of statuses but is implemented badly because can be just one so I would like to decode just the status object

struct StatusResponse: Codable {
    let state: String
    let error: String
    let id: String
    
    enum CodingKeys: String, CodingKey {
        case state = "state"
        case error = "errorCode"
        case id = "userId"
    }
}

I try to custom decode it

let container = try decoder.container(keyedBy: ContainerKeys.self)
var statuses = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .status)

but as expected I get "Expected to decode Dictionary<String, Any> but found an array instead." how I can have access to the first object from statuses variable and decode it into StatusResponse? or some other idea on how to procede?

AlexNica
  • 58
  • 8
  • To decode an array in a nested structure you have to use `NestedUnkeyedContainer` but then you run into an XY-Problem because you cannot use the same struct `StatusResponse ` to decode the root object **and** the child object. – vadian Mar 23 '22 at 18:45

3 Answers3

0

I would make a struct with field status to represent the top level object. That field is an array of StatusResponse:

struct TopLevelResponse: Codable {
   var status: [StatusResponse]
}

when decoding the json:

let decoded = JSONDecoder().decode(TopLevelResponse.self, from: data)
let first = decoded.status.first! // Already decoded!

Unless it's guaranteed that there will be at least one item in the array then you should handle a nil case.

atultw
  • 921
  • 7
  • 15
  • 1
    This is the easy way... I want the harder one ;) I would like to decode it. – AlexNica Mar 23 '22 at 18:34
  • This does decode it. What do you mean by harder one? – atultw Mar 23 '22 at 18:40
  • Yes it does, but you can have the problem if you want to access it many times you have to do this every time "decoded.status.first!", if you decode the actual object would be simpler and easier. – AlexNica Mar 23 '22 at 18:57
  • @AlexNica The answer you wrote is really the same thing. You're still decoding the top level response each time you call your init. If you don't want to decode each time then store the first status somewhere. – atultw Mar 23 '22 at 19:03
0

I will go with this solution inspired by this answer:

fileprivate struct RawStatusResponse: Decodable {
    let status: [RawStatus]

    struct RawStatus: Decodable {
         let state: String
         let errorCode: String
         let userId: String
    }
}

struct StatusResponse: Codable {
    let state: String
    let error: String
    let id: String
    
    enum CodingKeys: String, CodingKey {
        case state = "state"
        case error = "errorCode"
        case id = "userId"
    }

    public init(from decoder: Decoder) throws {
        let raw = try RawStatusResponse(from: decoder)
        
        state = raw.status.first!.state
        error = raw.status.first!.errorCode
        id = raw.status.first!.userId
    }
}

then when decode it just decode the actual object:

let state = try JSONDecoder().decode(StatusResponse, from: json)
AlexNica
  • 58
  • 8
0

You could decode it as a dictionary and use flatMap to get the array

let status = try JSONDecoder().decode([String: [StatusResponse]].self, from: data).flatMap(\.value)
Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52