Codable with nested JSON object

I have this server response from server

And I built its equivalent object as bellow:

 class BaseSoundModel:Codable
 {
    var related_item  : SubSoundModel = SubSoundModel()
 }

class SubSoundModel:Codable
{
    var parent : SoundModel? = SoundModel()
}

class SoundModel:Codable
{
    var sort : String? = ""
    var type : String? = ""
    var name : String? = ""
    var pic_file_path : String? = ""
    var text : String? = ""
    var related_item : SubSoundModel? = nil
}

but when I decode them I get nil parent response, what is the correct way to build equivalent class of my response?

See this recent thread which deals with a similar issue. I believe the problem here is that in SubSoundModel, the default implementation for Codable will look for a value under the key parent, but in the response you've posted the key is 6104 (which I'm assuming is some sort of entity ID that can change between responses).

As mentioned in that thread, you should probably decode to a [String: SoundModel] first, and then recreate a list of SubSoundModels from that dictionary once you have direct access to the dynamic keys.

if you see the related item key is keeping going deeper and deeper, which making the suggested solution not perfect for this situation.

I didn't try it, but I think all you need is changing from related_item: SubSoundModel? to related_item: SoundModel?, and use KeyedDecodingContainer.decodeIfPresent

That's because the key "parent" does not appear in the JSON object that you posted. In the future, please post it as text instead of an image.

But I have a dynamic key’s name

I don't know what you mean by that.

I meant if you see inside related items I have keys such as 13342 and 13342 and so on which don’t have constant names

And you're expecting the Codable API to magically read your mind and know that these number keys correspond to the parent property in SubSoundModel?

The synthesized implementation of Codable conformance requires each key in the JSON to exactly match each of the property names in your Swift types.

Hi and welcome @AliJalil! Is there a particular reason you're using classes instead of structs? It also looks like some properties are always available, so in those cases you could avoid optionals:

import Foundation

struct SoundModel: Codable {
  let sort: String
  let type: String
  let name: String
  let pic_file_path: String?
  let text: String
  let related_item: [String: SoundModel]?
}

struct BaseSoundModel: Codable {
  let related_item: SoundModel
}

let json = """
  {
    "related_item": {
      "sort": "0",
      "type": "sec_list",
      "name": "sub 6104",
      "pic_file_path": null,
      "text": "",
      "related_item": {
        "13342": {
          "sort": "3",
          "type": "sec_list",
          "name": "sub 13342",
          "pic_file_path": null,
          "text": "",
          "related_item": {
            "13343": {
              "sort": "0",
              "type": "sec_list",
              "name": "sub 6104",
              "pic_file_path": null,
              "text": ""
            }
          }
        },
        "13343": {
          "sort": "3",
          "type": "sec_list",
          "name": "sub 13343",
          "pic_file_path": null,
          "text": "",
          "related_item": {
            "13343": {
              "sort": "1",
              "type": "voice_list",
              "name": "item 13343",
              "pic_file_path": null,
              "text": "text 13343"
            }
          }
        }
      }
    }
  }
  """

let decoder = JSONDecoder()
if let data = json.data(using: .utf8),
   let base = try? decoder.decode(BaseSoundModel.self, from: data) {
  dump(base)
} else {
  print("Failed to decode!")
}

As others have already pointed out, the best way to handle dynamically named properties is to use dictionaries. In your attempt, the property parent cannot be found nor matched with "13342" or "13343".

@Peter-Schorn, If I had known that Codable API was reading my mind, I would not have asked, and I asked cause I didn't treat with such response before.

@xAlien95

Thanks for your reply, I used class instead of struct cause I guess I need to use self calling, i.e. may I need to use a property of type self.

I'll try your solution.

What do you mean by this?

Structs can have a property of type Self.

This is a little intemperate. There's a lot that is happening somewhat magically here, and people often struggle when the magic breaks down and they haven't ever quite grasped what it was doing before.

It doesn't look to me like the properties are really "dynamic". It looks like there's a fixed set of relationships between items, just like there's a fixed set of "sorts", and in the JSON schema's wisdom both of these are encoded as specific stringized numbers rather than specific strings of (say) text describing the relationships, which would be more readable. Regardless, I agree that the easiest thing is probably to deserialize this as a dictionary and then process it later.

Ali, I would strongly encourage you to immediately map this to a more natural-feeling static representation in Swift instead of writing a lot of code on such a dynamic representation. That probably means some sort of enum covering the different types of items, with each case knowing exactly which item data and related items it's expected to have.

2 Likes

I really seek for something instead of using dictionaries, but I didn't get it till now, my response represent a tree of items which are a library of sounds, each item represent a category and then sub category and sub category till leave of tree represent a sound with multiple singer and it's written words.
The number represent the parent category.