jsonswiftjsondecoder

Getting JSONDecoder, Decodable String or Integer


I'm trying Decodable from JSON result. Sometimes the result is Int/String. This code works, return Int(val) or String(val), but how to extract only val without Int(val) and String(val)?

enum StringOrInt: Decodable {
   case string(String)
   case int(Int)
        
   init(from decoder: Decoder) throws {
       if let int = try? decoder.singleValueContainer().decode(Int.self) {
            self = .int(int)
            return
       }
       if let string = try? decoder.singleValueContainer().decode(String.self) {
           self = .string(string)
           return
       }
            
        throw Error.couldNotFindStringOrInt
    }
    enum Error: Swift.Error {
        case couldNotFindStringOrInt
    }
}
    
struct properties: Decodable {
   let name: StringOrInt
}

let propData = feature.properties!
let resData = try? JSONDecoder.init().decode(properties.self, from: propData)
print(resData.name) 

result: string("str") or int(0), I need always "str" or "0".


Solution

  • You can use something called property wrapper. You can try to decode a String and if throws a type mismatch decoding error you can catch that error and try to decode your integer and store it as a string. This way it will only try to decode it again if the error is a type mismatch otherwise it will throw the error right away:

    @propertyWrapper
    struct StringOrInt: Decodable {
    
        var wrappedValue: String
    
        init(wrappedValue: String) {
            self.wrappedValue = wrappedValue
        }
    
        public init(from decoder: Decoder) throws {
            do {
                wrappedValue = try decoder.singleValueContainer().decode(String.self)
            } catch DecodingError.typeMismatch {
                wrappedValue = try decoder.singleValueContainer().decode(Int.self).string
            }
        }
    }
    

    extension LosslessStringConvertible {
        var string: String { .init(self) }
    }
    

    Usage:

    struct Properties: Decodable {
        @StringOrInt var name: String
    }