Swift Decodable of JSON which contains repetitive keys

i am stuck with decoding the following JSON sprites object (https://jsonblob.com/340d100d-747c-11eb-a6ca-bb7c9e892adf) because of repetitive keys that i don't understand how to represent in a Decodable Model. Currently i am using this Codable Model but i don't like the approach i am taking because it leads to create a lot of struct. Here is my current implementation which has a lot of repetitive code

Does anyone have any idea how this type of JSON would be mapped? I don't know if i should approach with struct + protocols or with class + inheritance

struct PokemonDetailsModel: Decodable {
   
   
   // MARK: Properties
   
   var name: String?
   var base_experience: Int?
   var order: Int?
   var sprites: PokemonSpritesModel?
   var stats: [PokemonStatsModel]?
   var types: [PokemonTypesModel]?
   
}


struct PokemonSpritesModel: Decodable {
   
   
   //MARK: Properties

   // ======== SET Of keys that are repeated

   var back_default: String?
   var back_female: String?
   var back_shiny: String?
   var back_shiny_female: String?
   var front_default: String?
   var front_female: String?
   var front_shiny: String?
   var front_shiny_female: String?

   // ======== SET Of keys that are repeated
   
   var other: PokemonSpritesModelOther?
   var versions: PokemonSpritesModelVersions?
   
   
}

struct PokemonSpritesModelOther: Decodable {
   
   //MARK: Properties
   var dreamWorld: PokemonSpritesModelOtherDreamWorld?
   var officialArtwork: PokemonSpritesModelOtherOfficialArtwork?
   
   
   
   private enum CodingKeys: String, CodingKey {
      case officialArtwork = "official-artwork", dreamWorld = "dream_world"
   }
   
   struct PokemonSpritesModelOtherDreamWorld: Decodable {
      
      //MARK: Properties
      var front_default: String?
      var front_female: String?
      
   }
   
   
   struct PokemonSpritesModelOtherOfficialArtwork: Decodable {
      
      //MARK: Properties
      var front_default: String?
      
   }
   
}

struct PokemonSpritesModelVersions: Decodable {
   
   
   //MARK: Properties
   var generation_i: PokemonSpritesModelVersionsGenerationsI?
   var generation_ii: PokemonSpritesModelVersionsGenerationsII?
   //   var generation_iii: PokemonSpritesModelVersionsGenerationsIII?
   //   var generation_iv: PokemonSpritesModelVersionsGenerationsIV?
   //   var generation_v: PokemonSpritesModelVersionsGenerationsV?
   //   var generation_vi: PokemonSpritesModelVersionsGenerationsVI?
   //   var generation_vii: PokemonSpritesModelVersionsGenerationsVII?
   //   var generation_viii: PokemonSpritesModelVersionsGenerationsVIII?
   
   
   
   private enum CodingKeys: String, CodingKey {
      case generation_i = "generation-i",
                  generation_ii = "generation-ii",
      //           generation_iii = "generation-iii",
      //           generation_iv = "generation-iv",
      //           generation_v = "generation-v",
      //           generation_vi = "generation-vi",
      //           generation_vii = "generation-vii",
      //           generation_viii = "generation-viii"
   }
   
   
   struct PokemonSpritesModelVersionsGenerationsI: Decodable {
      
      //MARK: Properties
      
      var red_blue: PokemonSpritesModelVersionsGenerationsColors?
      var yellow: PokemonSpritesModelVersionsGenerationsColors?
      
      private enum CodingKeys: String, CodingKey {
         case red_blue = "red-blue", yellow
      }
      
   }
   
   
   struct PokemonSpritesModelVersionsGenerationsII: Decodable {
      
      //MARK: Properties
      var crystal: PokemonSpritesModelVersionsGenerationsColors?
      var gold: PokemonSpritesModelVersionsGenerationsColors?
      var silver: PokemonSpritesModelVersionsGenerationsColors?
   }



   struct  PokemonSpritesModelVersionsGenerationsColors: Decodable {
      

      //****** Notes: Some of the keys here (i marked them with an asterisk) are not part of the Set of keys market in the 'sprites' json object that are shared across different models

      var back_default: String?
      var back_shiny: String?
      var back_gray: String? // *
      var back_female: String?
      var back_shiny_female: String?
      var front_default: String?
      var front_shiny: String?
      var front_gray: String? // *
      var front_female: String?
      var front_shiny_female: String?
      
   }

You can decode the data in any way you like if you write a custom implementation of init(from:) and encode(to:). How do you want to decode the data? How many structs do you want? How do you want the swift types to be structured? No one can read your mind.

Here's a model which reduces the number of nested structs:

struct PokemonDetailsModel: Decodable {
    
    // MARK: Properties
    
    var name: String?
    var base_experience: Int?
    var order: Int?
    var sprites: PokemonSpritesModel?
    
}

struct PokemonSpritesModel: Decodable {
    
    
    //MARK: Properties
    
    var back_default: String?
    var back_female: String?
    var back_shiny: String?
    var back_shiny_female: String?
    var front_default: String?
    var front_female: String?
    var front_shiny: String?
    var front_shiny_female: String?
    
    var dreamWorld: DreamWorld?
    var officialArtwork: OfficialArtwork?
    
    var generation_i: VersionsGenerationsI?
    var generation_ii: VersionsGenerationsII?
    
    init(from decoder: Decoder) throws {
        
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        do {
            self.back_default = try container.decodeIfPresent(
                String.self, forKey: .back_default
            )
            self.back_female = try container.decodeIfPresent(
                String.self, forKey: .back_female
            )
            self.back_shiny = try container.decodeIfPresent(
                String.self, forKey: .back_shiny
            )
            self.back_shiny_female = try container.decodeIfPresent(
                String.self, forKey: .back_shiny_female
            )
            self.front_default = try container.decodeIfPresent(
                String.self, forKey: .front_default
            )
            self.front_female = try container.decodeIfPresent(
                String.self, forKey: .front_female
            )
            self.front_shiny = try container.decodeIfPresent(
                String.self, forKey: .front_shiny
            )
            self.front_shiny_female = try container.decodeIfPresent(
                String.self, forKey: .front_shiny_female
            )
        }
        
        let otherContainer = try container.nestedContainer(
            keyedBy: CodingKeys.Other.self, forKey: .other
        )
        
        self.dreamWorld = try otherContainer.decodeIfPresent(
            DreamWorld.self,
            forKey: .dreamWorld
        )
        self.officialArtwork = try otherContainer.decodeIfPresent(
            OfficialArtwork.self,
            forKey: .officialArtwork
        )
        
        let versionsContainer = try container.nestedContainer(
            keyedBy: CodingKeys.Versions.self,
            forKey: .versions
        )
        self.generation_i = try versionsContainer.decodeIfPresent(
            VersionsGenerationsI.self,
            forKey: .generation_i
        )
        self.generation_ii = try versionsContainer.decodeIfPresent(
            VersionsGenerationsII.self,
            forKey: .generation_ii
        )
        
    }

    enum CodingKeys: String, CodingKey {
        case back_default
        case back_female
        case back_shiny
        case back_shiny_female
        case front_default
        case front_female
        case front_shiny
        case front_shiny_female
        
        case other
        enum Other: String, CodingKey {
            case dreamWorld = "dream_world"
            case officialArtwork = "official-artwork"
        }

        case versions
        enum Versions: String, CodingKey {
            case generation_i = "generation-i"
            case generation_ii = "generation-ii"
    //        case generation_iii = "generation-iii"
    //        case generation_iv = "generation-iv"
    //        case generation_v = "generation-v"
    //        case generation_vi = "generation-vi"
    //        case generation_vii = "generation-vii"
    //        case generation_viii = "generation-viii"
        }
        
    }

    struct DreamWorld: Decodable {
        var front_default: String?
        var front_female: String?
    }

    struct OfficialArtwork: Decodable {
        var front_default: String?
    }
    
    struct VersionsGenerationsI: Decodable {
        
        //MARK: Properties
        
        var red_blue: GenerationsColors?
        var yellow: GenerationsColors?
        
        private enum CodingKeys: String, CodingKey {
            case red_blue = "red-blue", yellow
        }
        
    }
    
    struct VersionsGenerationsII: Decodable {
        
        //MARK: Properties
        var crystal: GenerationsColors?
        var gold: GenerationsColors?
        var silver: GenerationsColors?
    }
    
    struct GenerationsColors: Decodable {
            
            
            //****** Notes: Some of the keys here (i marked them with an asterisk) are not part of the Set of keys market in the 'sprites' json object that are shared across different models
            
            var back_default: String?
            var back_shiny: String?
            var back_gray: String? // *
            var back_female: String?
            var back_shiny_female: String?
            var front_default: String?
            var front_shiny: String?
            var front_gray: String? // *
            var front_female: String?
            var front_shiny_female: String?
            
        }
    
}
1 Like

Sorry, for the delay, thank you for the solution it seems good to me. I was wondering if there is a way to reuse the first 8 vars of the class PokemonSpritesModel by improving the use of nested containers or maybe a more customized decoder?

I don't know what you mean by "reuse the first 8 vars of the class PokemonSpritesModel"