How to Parse Complicated JSON for CoreData Using Decodable Protocol

Find a perfect JSON parser for CoreData - decodable from Apple

Hello dear colleagues and those who work with iOS. If you have some basic awareness about the Decodable protocol, you can parse a simple JSON in a minute and lots of sites have multiple examples of how to parse JSON. Certain problems begin, when you receive JSON with several levels of nesting, it’s time when you have to sweat a bit to parse it. Here I’d like to share my experience of parsing the JSON with several levels of nesting in coreData. I will not describe how to make a request in order to receive data from the server, I will just use JSON, which is stored on the disk and which we will parse and record into the CoreDate.

Experience of Parsing the JSON

So we have a small JSON, it has certain objects to be displayed under the key result. These objects in their turn have a massive of friends, which should also be displayed.

{
    "studios": [
        "Marvel",
        "Warner Brothers",
        "DC"
    ],
    "results": [{
        "name": "Daffy Duck",
        "id": 548001360,
        "avatarPath": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ7leBgakzSpfs50MeDnkd39Lu_nZpyHIE_0tm3FzovjaGhiF7K",
        "friends": [{
            "name": "Bugs Bunny",
            "id": 825382838,
            "avatarPath": "https://avatarfiles.alphacoders.com/812/81220.webp"
        },
        {
            "name": "Porky Pig",
            "id": 819263082,
            "avatarPath": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTEQcXKKj5YfDTIkNPcBBNyLrCv7M5V4LiwFh1n8VJy6H5RJrBKew"
        }
        ]
    },
    {
        "name": "Porky Pig",
        "id": 819263082,
        "avatarPath": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTEQcXKKj5YfDTIkNPcBBNyLrCv7M5V4LiwFh1n8VJy6H5RJrBKew",
        "friends": [{
            "name": "Bugs Bunny",
            "id": 825382838,
            "avatarPath": "https://avatarfiles.alphacoders.com/812/81220.webp"
        },
        {
            "name": "Daffy Duck",
            "id": 548001360,
            "avatarPath": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ7leBgakzSpfs50MeDnkd39Lu_nZpyHIE_0tm3FzovjaGhiF7K"
        }
        ]
    },
    {
        "name": "Bugs Bunny",
        "id": 825382838,
        "avatarPath": "https://avatarfiles.alphacoders.com/812/81220.webp",
        "friends": [{
            "name": "Porky Pig",
            "id": 819263082,
            "avatarPath": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTEQcXKKj5YfDTIkNPcBBNyLrCv7M5V4LiwFh1n8VJy6H5RJrBKew"
        },
        {
            "name": "Daffy Duck",
            "id": 548001360,
            "avatarPath": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ7leBgakzSpfs50MeDnkd39Lu_nZpyHIE_0tm3FzovjaGhiF7K"
        }
        ]
    }
    ]
}

So to parse everything properly and without any problems, I’ve found such a structure in the net, which is able to take the necessary data from the container by the given key.

struct DecodingHelper {
 
    /// Dynamic key
    private struct Key: CodingKey {
        let stringValue: String
        init?(stringValue: String) {
            self.stringValue = stringValue
            self.intValue = nil
        }
 
        let intValue: Int?
        init?(intValue: Int) {
            return nil
        }
    }
 
    /// Dummy model that handles model extracting logic from a key
    private struct ContainerResponse<NestedModel: Decodable>: Decodable {
        let nested: NestedModel
 
        public init(from decoder: Decoder) throws {
            let key = Key(stringValue: decoder.userInfo[CodingUserInfoKey(rawValue: "key")!]! as! String)!
            let values = try decoder.container(keyedBy: Key.self)
            nested = try values.decode(NestedModel.self, forKey: key)
        }
    }
 
    static func decode<T: Decodable>(modelType: T.Type, fromKey key: String, data: Data) throws -> T {
        let decoder = JSONDecoder()
 
        // ***Pass in our key through `userInfo`
        decoder.userInfo[CodingUserInfoKey(rawValue: "key")!] = key
        let model = try decoder.decode(ContainerResponse<T>.self, from: data).nested
        return model
    }
}

It’s rather convenient to work with such a structure.

let craracterValues = try DecodingHelper.decode(modelType: [Character].self, fromKey: "results", data: jsonData)

We have a static method, which allows me to make such a request and to set all the parameters: the type of the model I need to return, the key under which the model lies, and, obviously, the very data I need. Let’s look at the JSON we have. Each nesting level should be represented as a separate container with a data corresponding to a certain key.

Any data can be stored in the container, a massive with dictionaries, for instance, as well as a single data value like Int.

For example, to deparse friends in such an object:

    {
        "name": "Porky Pig",
        "id": 819263082,
        "avatarPath": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTEQcXKKj5YfDTIkNPcBBNyLrCv7M5V4LiwFh1n8VJy6H5RJrBKew",
        "friends": [{
            "name": "Bugs Bunny",
            "id": 825382838,
            "avatarPath": "https://avatarfiles.alphacoders.com/812/81220.webp"
        },
        {
            "name": "Daffy Duck",
            "id": 548001360,
            "avatarPath": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ7leBgakzSpfs50MeDnkd39Lu_nZpyHIE_0tm3FzovjaGhiF7K"
        }
        ]
    }

We need to take

  let friendsContainer = try? generalContainer.nestedUnkeyedContainer(forKey: .friends)

container by the key .friends. from the main container child.

In fact, our new container will be a massive of objects and to deparse the objects we need to loop through it.

   if var friendsContainer = friendsContainer {
            var friends: [Character] = []
 
            while !friendsContainer.isAtEnd {
                if let friend = try friendsContainer.decodeIfPresent(Character.self) {
                    friends.append(friend)
                }
            }
 
            self.friends = NSSet(array: friends)
        }

friendsContainer has the following property.isAtEnd. So we, in fact, iterate and create objects until the property returns “true”, which will mean the end of our massive.

Conclusion

Decodable protocol — is an extremely powerful tool, which is given to us for free by Apple. Thanks to it! The only thing you should do is study it, otherwise, you won’t be able to parse certain multi-level JSON objects.

Dear colleagues, I wish you luck and hope the article was useful for you.