1

I am retrieving a complex nested object from my JSON REST API.

DocumentDraft
 - uuid: String
 - schema: Schema // Very complicated object with many variations
 - url: String
 - values: [Value]
 - successors: [String]
 - predecessors: [String]

Value
 - key: String
 - val: String? OR [String]?   // <-- This is the problem

I suppose the proper way to deal with this is to introduce a generic type.

struct Value<V: Decodable>: Decodable {
  let key: String
  let val: V?
}

... but even so, values could be a mixed array, so I do not see how declaring what V is would help.

But then, of course the generic type propagates all the way up the hierarchy, to the DocumentDraft object, to the publisher, to my API calls, etc. polluting the entire chain of otherwise very clean and readable calls and objects. I would like to deal with this only on the level of Value, and let the JSONDecoder simply return one of the two somehow.

Is there another way to deal with the two possibilities of the optional val as either String or [String] without changing the entire parent object?

Mundi
  • 79,884
  • 17
  • 117
  • 140

1 Answers1

4

You can achieve that using only the [String] type and manually implementing the init(from:) function of Decodable protocol like this:

struct Value: Decodable {
    let key: String
    let val: [String]?
    
    enum CodingKeys: String, CodingKey {
        case key, val
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        key = try container.decode(String.self, forKey: .key)
        do {
            if let string = try container.decodeIfPresent(String.self, forKey: .val) {
                val = [string]
            } else {
                val = nil
            }
        } catch DecodingError.typeMismatch {
            val = try container.decodeIfPresent([String].self, forKey: .val)
        }
    }
}

When decoding to String value succeeds, create an array of strings with only one element. When decoding to String value fails, try to decode as [String]

gcharita
  • 7,729
  • 3
  • 20
  • 37
  • 1
    This is a great approach, but as written this will ignore malformed JSON when it should throw an error. You can improve this by using a do-catch block rather than if-let and `try decodeIfPresent` rather than `try? decode`. – Rob Napier Aug 19 '20 at 13:16
  • @RobNapier you are right. I updated my answer with your suggestion. – gcharita Aug 19 '20 at 13:31