Home > OS >  Need to map the json to model in Swift
Need to map the json to model in Swift

Time:02-04

I have a Json response that i get from a API, the json looks like something like this

{
    "data": [
        {
            "_id": "63d9d2d57c0cfe791b2b19f6",
            "step": {
                "_id": "step1",
                "status": "STARTED",
                "children": [
                    {
                        "_id": "step2",
                        "status": "NOT_STARTED",
                        "children": [
                            {
                                "_id": "step3",
                                "status": "NOT_STARTED",
                                "children": [
                                    {
                                        "_id": "step3",
                                        "status": "NOT_STARTED"
                                    }
                                ]
                            }
                        ]
                    }
                ]
            },
            "status": "IN_PROGRESS",
            "createdBy": "2700930039"
        }
    ]
}

The json can have multiple levels of children objects inside each other. I need to map this json response to Models in swift

Here are the Models for the Json that i created

struct NestedJsonModel:Decodable {
   var data:[LotData]
}

struct LotData:Decodable {
   var _id: String
   var step: StepDetails
   var status: String
   var createdBy: String
}

struct StepDetails:Decodable {
   var _id: String
   var status: String
   var children: [ChildSteps]
}

struct ChildSteps:Decodable {
   var _id: String
   var status: String
   var children: [StepDetails] //because children contains the same model as Step Details
}

Here is the decoder code

let jsonData = data.data(using: .utf8)!
do {
    let decoder = JSONDecoder()
    let tableData = try decoder.decode(NestedJsonModel.self, from: jsonData)
    result = tableData.data
    print("****************")
    print(result!)
    print("****************")
}
catch {
    print (error)
}

But i keep getting this error

keyNotFound(CodingKeys(stringValue: "children", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "step", intValue: nil), CodingKeys(stringValue: "children", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "children", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"children\", intValue: nil) (\"children\").", underlyingError: nil))

CodePudding user response:

Notice that the innermost layer of your JSON does not have a "children" key, but your ChildSteps struct does have a non-optional children property that needs to be assigned some value.

To fix this, you just need to make children have an optional type.

Also note that since StepDetails and ChildSteps have the same properties, you can merge them too.

struct NestedJsonModel:Decodable {
   var data:[LotData]
}

struct LotData:Decodable {
   var _id: String
   var step: StepDetails
   var status: String
   var createdBy: String
}

struct StepDetails:Decodable {
   var _id: String
   var status: String
   var children: [StepDetails]?
}

If you don't need to differentiate between "having an empty children array" and "not having the children key at all", then you can make the children property non-optional, and initialise it with an empty array when the key is not found.

This can be conveniently done without handwriting your own custom decoding logic by using a property wrapper as shown in this answer:

@propertyWrapper
struct DefaultEmptyArray<T:Decodable>: Decodable {
    var wrappedValue: [T] = []
    
    init() {}
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        wrappedValue = try container.decode([T].self)
    }
}

extension KeyedDecodingContainer {
    func decode<T:Decodable>(_ type: DefaultEmptyArray<T>.Type,
                forKey key: Key) throws -> DefaultEmptyArray<T> {
        try decodeIfPresent(type, forKey: key) ?? .init()
    }
}

// in StepDetails
@DefaultEmptyArray var children: [StepDetails]?
  • Related