Find a perfect JSON parser for CoreData - decodable from Apple

Find a perfect JSON parser for CoreData - decodable from Apple

Hello dear colleagues and those who work with iOS. Most of you have definitely dealt with a net and parsed data from JSON. There are plenty of data parsing software, libraries and tools, which can be used for the purpose. Some of them are simple and some are complex. Frankly speaking, I have been parsing by hand for a long time, not entrusting the process to any side library or json parser. I should admit there were certain advantages in the process.

But then I discovered a useful parsing tool — decodable protocol, which can parse data instead of the user. It was some kind of magic for me at first, especially when JSON came from the server without high nesting level.

If you have some basic awareness about the Decodable protocol, you can deparse 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 do 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. Let the Magic begin!

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.