Home > Mobile >  just one object nested in an array, how to decode the object?
just one object nested in an array, how to decode the object?

Time:03-24

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?

CodePudding user response:

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.

CodePudding user response:

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)

CodePudding user response:

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)
  • Related