【问题标题】:Any when decoding JSON with Codable?使用 Codable 解码 JSON 时有什么?
【发布时间】:2018-02-27 00:46:07
【问题描述】:

使用 Swift 3 JSONSerialization,如果您的数据模型的一部分是完全动态的,您总是可以将反序列化的数据留在 Any 并让消费者处理它。

使用 Swift 4 的 Codable,我想这样做:

struct Foo : Codable {
    let bar: Any;
}

但我明白了

JSONPlayground.playground:4:9: note: cannot automatically synthesize 'Decodable' because 'Any' does not conform to 'Decodable'
    let bar: Any;
        ^

如果我能实现我自己的Decodable,仅此一项就不会是世界末日,但这需要Decoder 支持解码为Any,据我所知,它不支持.例如:

extension Foo {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let result = try container.decode(AnyClass.self)
    }
}

给予

error: JSONPlayground.playground:4:36: error: cannot invoke 'decode' with an argument list of type '(AnyClass.Protocol)'
        let result = try container.decode(AnyClass.self)
                                   ^

有什么解决办法吗?

【问题讨论】:

  • 认为您可能无法尝试解码 Any,但也许您可以尝试将 bar 转换为 JSON 字符串,然后以这种方式存储
  • Codable 不会将数字解码为字符串。
  • @LorenzoB 我正在开发quicktype,这是一个用于从 JSON 数据为多种编程语言生成静态类型和序列化器/反序列化器的工具。在某些情况下,我们无法从数据中推断出类型(有关详细信息,请参阅 my blog post),因此我们默认为目标语言的 Any
  • 如果您根本不知道 JSON 中的数据结构处于给定点,那么您目前不走运,因为这还不能有效地完成/以合理的方式。见bugs.swift.org/browse/SR-5311

标签: json swift swift4


【解决方案1】:

我最终不得不实现自己的类来编码/解码Any 值。它不漂亮,但它似乎工作:

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
    }
}

【讨论】:

  • 这太好了,谢谢。我只需要将 添加到 KeyedEncodingContainer,这会导致构建错误。
  • 谢谢,它非常适合解析 JSON,不过我想知道,你能告诉我如何将单个值写入现有的 JSONAny 吗?
  • 这在 Swift 5 中有效吗?我让 XCode 抱怨 Reference to generic type 'KeyedDecodingContainer' requires arguments in <...>,并试图用无效的修复“修复”它
【解决方案2】:

我解决了创建 AnyValue 结构以允许对 JSON 中的 Any 值进行编码和解码的问题:

使用起来非常简单:

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 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
    }
}


我也创建了一个 JsonUtil 结构

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
        }
    }
}

如果您需要像 [String] 这样的新类型,只需将其添加到 AnyValue 结构上。

祝你好运:)

【讨论】:

  • 这抵消了Codable 协议的所有优点,并且对于大型数据集来说可能非常昂贵。
  • 这是对泛型的滥用。几乎所有情况下,JSON 字符串都不是通用的。
  • 它还使用产品类型(结构)而不是总和类型(枚举)。您将不断打开可选选项,并且总体上过得很糟糕
【解决方案3】:

您只需使用来自 Matt Thompson 的酷库 AnyCodable 中的 AnyCodable 类型。

例如:

import AnyCodable

struct Foo: Codable
{
    var bar: AnyCodable
}

【讨论】:

  • 如果我需要在if else条件下使用变量,如何获取变量bar的数据类型
  • if foo.bar is YourType {}if let bar = foo.bar as? YourType {} 我希望这会工作
【解决方案4】:

你可以试试BeyovaJSON

import BeyovaJSON

struct Foo: Codable {
  let bar: JToken
}

let foo = try! JSONDecoder().decode(Foo.self, from: data)

【讨论】:

    猜你喜欢
    • 2021-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-17
    • 2019-03-03
    • 1970-01-01
    • 2018-11-10
    • 1970-01-01
    相关资源
    最近更新 更多