Any when decoding JSON with Codable?

后端 未结 4 1915
情深已故
情深已故 2020-12-06 08:32

With Swift 3 JSONSerialization, if a part of your data model was completely dynamic, you could always just leave the deserialized data at Any and l

相关标签:
4条回答
  • 2020-12-06 08:59

    I ended up having to implement my own class to encode/decode Any values. It's not pretty, but it seems to work:

    class JSONAny: Codable {
        public let value: Any
    
        static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError {
            let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny")
            return DecodingError.typeMismatch(JSONAny.self, context)
        }
    
        static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError {
            let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny")
            return EncodingError.invalidValue(value, context)
        }
    
        static func decode(from container: SingleValueDecodingContainer) throws -> Any {
            if let value = try? container.decode(Bool.self) {
                return value
            }
            if let value = try? container.decode(Int64.self) {
                return value
            }
            if let value = try? container.decode(Double.self) {
                return value
            }
            if let value = try? container.decode(String.self) {
                return value
            }
            if container.decodeNil() {
                return JSONNull()
            }
            throw decodingError(forCodingPath: container.codingPath)
        }
    
        static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any {
            if let value = try? container.decode(Bool.self) {
                return value
            }
            if let value = try? container.decode(Int64.self) {
                return value
            }
            if let value = try? container.decode(Double.self) {
                return value
            }
            if let value = try? container.decode(String.self) {
                return value
            }
            if let value = try? container.decodeNil() {
                if value {
                    return JSONNull()
                }
            }
            if var container = try? container.nestedUnkeyedContainer() {
                return try decodeArray(from: &container)
            }
            if var container = try? container.nestedContainer(keyedBy: MyCodingKey.self) {
                return try decodeDictionary(from: &container)
            }
            throw decodingError(forCodingPath: container.codingPath)
        }
    
        static func decode(from container: inout KeyedDecodingContainer, forKey key: MyCodingKey) throws -> Any {
            if let value = try? container.decode(Bool.self, forKey: key) {
                return value
            }
            if let value = try? container.decode(Int64.self, forKey: key) {
                return value
            }
            if let value = try? container.decode(Double.self, forKey: key) {
                return value
            }
            if let value = try? container.decode(String.self, forKey: key) {
                return value
            }
            if let value = try? container.decodeNil(forKey: key) {
                if value {
                    return JSONNull()
                }
            }
            if var container = try? container.nestedUnkeyedContainer(forKey: key) {
                return try decodeArray(from: &container)
            }
            if var container = try? container.nestedContainer(keyedBy: MyCodingKey.self, forKey: key) {
                return try decodeDictionary(from: &container)
            }
            throw decodingError(forCodingPath: container.codingPath)
        }
    
        static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] {
            var arr: [Any] = []
            while !container.isAtEnd {
                let value = try decode(from: &container)
                arr.append(value)
            }
            return arr
        }
    
        static func decodeDictionary(from container: inout KeyedDecodingContainer) throws -> [String: Any] {
            var dict = [String: Any]()
            for key in container.allKeys {
                let value = try decode(from: &container, forKey: key)
                dict[key.stringValue] = value
            }
            return dict
        }
    
        static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws {
            for value in array {
                if let value = value as? Bool {
                    try container.encode(value)
                } else if let value = value as? Int64 {
                    try container.encode(value)
                } else if let value = value as? Double {
                    try container.encode(value)
                } else if let value = value as? String {
                    try container.encode(value)
                } else if value is JSONNull {
                    try container.encodeNil()
                } else if let value = value as? [Any] {
                    var container = container.nestedUnkeyedContainer()
                    try encode(to: &container, array: value)
                } else if let value = value as? [String: Any] {
                    var container = container.nestedContainer(keyedBy: MyCodingKey.self)
                    try encode(to: &container, dictionary: value)
                } else {
                    throw encodingError(forValue: value, codingPath: container.codingPath)
                }
            }
        }
    
        static func encode(to container: inout KeyedEncodingContainer, dictionary: [String: Any]) throws {
            for (key, value) in dictionary {
                let key = MyCodingKey(stringValue: key)!
                if let value = value as? Bool {
                    try container.encode(value, forKey: key)
                } else if let value = value as? Int64 {
                    try container.encode(value, forKey: key)
                } else if let value = value as? Double {
                    try container.encode(value, forKey: key)
                } else if let value = value as? String {
                    try container.encode(value, forKey: key)
                } else if value is JSONNull {
                    try container.encodeNil(forKey: key)
                } else if let value = value as? [Any] {
                    var container = container.nestedUnkeyedContainer(forKey: key)
                    try encode(to: &container, array: value)
                } else if let value = value as? [String: Any] {
                    var container = container.nestedContainer(keyedBy: MyCodingKey.self, forKey: key)
                    try encode(to: &container, dictionary: value)
                } else {
                    throw encodingError(forValue: value, codingPath: container.codingPath)
                }
            }
        }
    
        static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws {
            if let value = value as? Bool {
                try container.encode(value)
            } else if let value = value as? Int64 {
                try container.encode(value)
            } else if let value = value as? Double {
                try container.encode(value)
            } else if let value = value as? String {
                try container.encode(value)
            } else if value is JSONNull {
                try container.encodeNil()
            } else {
                throw encodingError(forValue: value, codingPath: container.codingPath)
            }
        }
    
        public required init(from decoder: Decoder) throws {
            if var arrayContainer = try? decoder.unkeyedContainer() {
                self.value = try JSONAny.decodeArray(from: &arrayContainer)
            } else if var container = try? decoder.container(keyedBy: MyCodingKey.self) {
                self.value = try JSONAny.decodeDictionary(from: &container)
            } else {
                let container = try decoder.singleValueContainer()
                self.value = try JSONAny.decode(from: container)
            }
        }
    
        public func encode(to encoder: Encoder) throws {
            if let arr = self.value as? [Any] {
                var container = encoder.unkeyedContainer()
                try JSONAny.encode(to: &container, array: arr)
            } else if let dict = self.value as? [String: Any] {
                var container = encoder.container(keyedBy: MyCodingKey.self)
                try JSONAny.encode(to: &container, dictionary: dict)
            } else {
                var container = encoder.singleValueContainer()
                try JSONAny.encode(to: &container, value: self.value)
            }
        }
    }
    
    class JSONNull: Codable {
        public init() {
        }
    
        public required init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            if !container.decodeNil() {
                throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
            }
        }
    
        public func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            try container.encodeNil()
        }
    }
    
    class MyCodingKey : CodingKey {
        let key: String
    
        required init?(intValue: Int) {
            return nil
        }
    
        required init?(stringValue: String) {
            key = stringValue
        }
    
        var intValue: Int? {
            return nil
        }
    
        var stringValue: String {
            return key
        }
    }
    
    0 讨论(0)
  • 2020-12-06 08:59

    I solved my problem creating an AnyValue struct to allow Encode and Decode Any values from JSON:

    To use it is pretty simple:

    class MyClass: Codable {
    
        var data: [String: AnyValue?]?
    
        init(data: [String: AnyValue?]) {
            self.data = data
        }
    }
    
    
    let data = ["a": AnyValue(3), "b": AnyValue(true), "c": AnyValue("Rodrigo"), "d": AnyValue(3.3)]
    let myClass = MyClass(data: data)
    
    if let json = JsonUtil<MyClass>.toJson(myClass) {
        print(json) // {"data":{"d":3.3,"b":true,"c":"Rodrigo","a":3}}
    
        if let data = JsonUtil<MyClass>.from(json: json)?.data {
            print(data["a"]??.value() ?? "nil") // 3
            print(data["b"]??.value() ?? "nil") // true
            print(data["c"]??.value() ?? "nil") // Rodrigo
            print(data["d"]??.value() ?? "nil") // 3.3
        }
    }
    


    AnyValue struct:

    struct AnyValue: Codable {
    
        private var int: Int?
        private var string: String?
        private var bool: Bool?
        private var double: Double?
    
        init(_ int: Int) {
            self.int = int
        }
    
        init(_ string: String) {
            self.string = string
        }
    
        init(_ bool: Bool) {
            self.bool = bool
        }
    
        init(_ double: Double) {
            self.double = double
        }
    
        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
            }
    
            if let bool = try? decoder.singleValueContainer().decode(Bool.self) {
                self.bool = bool
                return
            }
    
            if let double = try? decoder.singleValueContainer().decode(Double.self) {
                self.double = double
            }
        }
    
    
        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
    
            if let anyValue = self.value() {
                if let value = anyValue as? Int {
                    try container.encode(value)
                    return
                }
    
                if let value = anyValue as? String {
                    try container.encode(value)
                    return
                }
    
                if let value = anyValue as? Bool {
                    try container.encode(value)
                    return
                }
    
                if let value = anyValue as? Double {
                    try container.encode(value)
                    return
                }
            }
    
            try container.encodeNil()
        }
    
    
        func value() -> Any? {
            return self.int ?? self.string ?? self.bool ?? self.double
        }
    }
    


    And I created a JsonUtil struct too:

    struct JsonUtil<T: Codable>  {
    
        public static func from(json: String) -> T? {
            if let jsonData = json.data(using: .utf8) {
                let jsonDecoder = JSONDecoder()
    
                do {
                    return try jsonDecoder.decode(T.self, from: jsonData)
    
                } catch {
                    print(error)
                }
            }
    
            return nil
        }
    
        public static func toJson(_ obj: T) -> String? {
            let jsonEncoder = JSONEncoder()
    
            do {
                let jsonData = try jsonEncoder.encode(obj)
                return String(data: jsonData, encoding: String.Encoding.utf8)
    
            } catch {
                print(error)
                return nil
            }
        }
    }
    

    If you need a new type like [String] per example, you just need add it on AnyValue struct.

    Good luck :)

    0 讨论(0)
  • 2020-12-06 09:17

    Simply you can use AnyCodable type from Matt Thompson's cool library AnyCodable.

    Eg:

    import AnyCodable
    
    struct Foo: Codable
    {
        var bar: AnyCodable
    }
    
    0 讨论(0)
  • 2020-12-06 09:18

    You could try BeyovaJSON

    import BeyovaJSON
    
    struct Foo: Codable {
      let bar: JToken
    }
    
    let foo = try! JSONDecoder().decode(Foo.self, from: data)
    
    0 讨论(0)
提交回复
热议问题