skip to Main Content

Consider the following JSON:

{
  "jsonName": "fluffy",
  "color1": "Blue",
  "color2": "Red",
  "color3": "Green",
  "color4": "Yellow",
  "color5": "Purple"
}

And the model object:

struct Cat: Decodable {
    let name: String
    let colors: [String]

    private enum CodingKeys: String, CodingKey {
        case name = "jsonName"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
    
        // How to parse all the items with colorX into the colors property???
    }   
}

There can be up to 10 colors, but some of them may be empty strings.

I’ve tried several variations of the decode method but I can’t figure out how to use an identifier like color(i).

3

Answers


  1. One simple but maybe not so elegant solution is to decode the json as a dictionary

    let result = try JSONDecoder().decode([String: String].self, from: data)
    

    and then add an init to the custom type that takes a dictionary as the argument

    extension String: Error {} //Replace with custom error type
    
    init(dictionary: [String: String]) throws {
        guard let name = dictionary["jsonName"] else { throw "No key jsonName was found") }
        self.name = name
        colors = dictionary.values.filter { $0.starts(with: "color") }
    }
    
    Login or Signup to reply.
  2. As with so many of these problems, the tool to start with is AnyCodingKey:

    public struct AnyCodingKey: CodingKey, CustomStringConvertible, ExpressibleByStringLiteral,
                                ExpressibleByIntegerLiteral, Hashable, Comparable {
        public var description: String { stringValue }
        public let stringValue: String
        public init(_ string: String) { self.stringValue = string }
        public init?(stringValue: String) { self.init(stringValue) }
        public var intValue: Int?
        public init(intValue: Int) {
            self.stringValue = "(intValue)"
            self.intValue = intValue
        }
        public init(stringLiteral value: String) { self.init(value) }
        public init(integerLiteral value: Int) { self.init(intValue: value) }
        public static func < (lhs: AnyCodingKey, rhs: AnyCodingKey) -> Bool {
            lhs.stringValue < rhs.stringValue
        }
    }
    

    You can build this more simply, but this implementation is kind of nice.

    With that, one possible decoder looks like this:

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: AnyCodingKey.self)
        name = try container.decode(String.self, forKey: "jsonName")
    
        var colors: [String] = []
        for key in container.allKeys where key.stringValue.hasPrefix("color") {
            colors.append(try container.decode(String.self, forKey: key))
        }
        self.colors = colors
    }
    

    This decodes "jsonName" as name, and then puts anything starting with "color" into colors. You can adapt to a wide variety of specific use cases.

    Login or Signup to reply.
  3. try this simple approach using a while loop inside the init(from decoder: Decoder)

    struct Cat: Decodable {
        let name: String
        var colors: [String] // <-- here var
        
        private enum CodingKeys: String, CodingKey {
            case name = "jsonName"
        }
        
        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            self.name = try values.decode(String.self, forKey: .name)
            
            // -- here
            self.colors = []
            let container = try decoder.singleValueContainer()
            let colours = try container.decode([String: String?].self)
            var index = 1
            while
                let col = colours["color(index)"] as? String,
                !col.isEmpty
            {
                self.colors.append(col)
                index += 1
            }
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search