如何处理完全动态的JSON响应

如何处理完全动态的JSON响应

本文介绍了如何处理完全动态的JSON响应的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

也许社区中有人遇到过类似的挣扎,并提出了可行的解决方案.

Maybe someone in the community has had similar struggles and have come up with a workable solution.

我们目前正在开发多语言键/值存储.鉴于此,我们通常将不知道将要提前存储的内容.

We're currently working on a polyglot key/value store. Given this, we'll generally have no knowledge of what will be stored ahead of time.

考虑以下结构

struct Character : Codable, Equatable {
    let name:    String
    let age:     Int
    let gender:  Gender
    let hobbies: [String]

    static func ==(lhs: Character, rhs: Character) -> Bool {
        return (lhs.name == rhs.name
                   && lhs.age == rhs.age
                   && lhs.gender == rhs.gender
                   && lhs.hobbies == rhs.hobbies)
    }
}

通过网络发送/接收角色实体时,一切都非常简单.用户可以向我们提供我们可以解码成的类型.

When sending/receiving Character entities over the wire, everything is fairly straight forward. The user can provide us the Type in which we can decode into.

但是,我们确实能够动态查询后端中存储的实体.例如,我们可以请求'name'属性的值并将其返回.

However, we do have the ability to dynamically query the entities stored within the backend. For example, we can request the value of the 'name' property and have that returned.

这种动力是一个痛点.除了不知道属性的类型是否可编码外,返回的格式也可以是动态的.

This dynamism is a pain point. In addition to not knowing the type of the properties outside of the fact that they are Codable, the format that is returned can be dynamic as well.

以下是对两个提取属性的调用的响应示例:

Here's some examples of response for two different calls extracting properties:

{"value":"Bilbo"}

{"value":["[Ljava.lang.Object;",["Bilbo",111]]}

在某些情况下,它可能等同于字典.

In some cases, it could be an equivalent of a dictionary.

现在,我具有以下结构来处理响应:

Right now, I have the following structs for dealing with responses:

fileprivate struct ScalarValue<T: Decodable> : Decodable {
    var value: T?
}

使用字符"示例,传递给解码器的类型为:

Using the Character example, the type passed to the decoder would be:

ScalarValue<Character>.self

但是,对于单个值,数组或字典的情况,我有些困惑.

However, for the single value, array, or dictionary case, I'm somewhat stuck.

我从类似的东西开始

fileprivate struct AnyDecodable: Decodable {
    init(from decoder: Decoder) throws {
        // ???
    }
}

根据我上面描述的可能的返回类型,我不确定当前的API是否可以实现此目标.

Based on the possible return types I've described above, I'm not sure if this is possible with the current API.

有想法吗?

推荐答案

Swift绝对可以处理任意可解码的JSON.这与任意可解码的东西不同. JSON无法编码所有可能的值.但是此结构将解码可以用JSON表示的所有内容,然后您就可以从那里以类型安全的方式探索它,而不必求助于Any之类的危险而笨拙的工具.

Swift can definitely handle an arbitrary JSON decodable. This isn't the same thing as an arbitrary decodable. JSON can't encode all possible values. But this structure will decode anything that can be expressed in JSON, and from there you can explore it in a type-safe way without resorting to dangerous and awkward tools like Any.

enum JSON: Decodable, CustomStringConvertible {
    var description: String {
        switch self {
        case .string(let string): return "\"\(string)\""
        case .number(let double):
            if let int = Int(exactly: double) {
                return "\(int)"
            } else {
                return "\(double)"
            }
        case .object(let object):
            return "\(object)"
        case .array(let array):
            return "\(array)"
        case .bool(let bool):
            return "\(bool)"
        case .null:
            return "null"
        }
    }

    var isEmpty: Bool {
        switch self {
        case .string(let string): return string.isEmpty
        case .object(let object): return object.isEmpty
        case .array(let array): return array.isEmpty
        case .null: return true
        case .number, .bool: return false
        }
    }

    struct Key: CodingKey, Hashable, CustomStringConvertible {
        var description: String {
            return stringValue
        }

        var hashValue: Int { return stringValue.hash }

        static func ==(lhs: JSON.Key, rhs: JSON.Key) -> Bool {
            return lhs.stringValue == rhs.stringValue
        }

        let stringValue: String
        init(_ string: String) { self.stringValue = string }
        init?(stringValue: String) { self.init(stringValue) }
        var intValue: Int? { return nil }
        init?(intValue: Int) { return nil }
    }

    case string(String)
    case number(Double) // FIXME: Split Int and Double
    case object([Key: JSON])
    case array([JSON])
    case bool(Bool)
    case null

    init(from decoder: Decoder) throws {
        if let string = try? decoder.singleValueContainer().decode(String.self) { self = .string(string) }
        else if let number = try? decoder.singleValueContainer().decode(Double.self) { self = .number(number) }
        else if let object = try? decoder.container(keyedBy: Key.self) {
            var result: [Key: JSON] = [:]
            for key in object.allKeys {
                result[key] = (try? object.decode(JSON.self, forKey: key)) ?? .null
            }
            self = .object(result)
        }
        else if var array = try? decoder.unkeyedContainer() {
            var result: [JSON] = []
            for _ in 0..<(array.count ?? 0) {
                result.append(try array.decode(JSON.self))
            }
            self = .array(result)
        }
        else if let bool = try? decoder.singleValueContainer().decode(Bool.self) { self = .bool(bool) }
        else {
            self = .null
        }
    }

    var objectValue: [String: JSON]? {
        switch self {
        case .object(let object):
            let mapped: [String: JSON] = Dictionary(uniqueKeysWithValues:
                object.map { (key, value) in (key.stringValue, value) })
            return mapped
        default: return nil
        }
    }

    var arrayValue: [JSON]? {
        switch self {
        case .array(let array): return array
        default: return nil
        }
    }

    subscript(key: String) -> JSON? {
        guard let jsonKey = Key(stringValue: key),
            case .object(let object) = self,
            let value = object[jsonKey]
            else { return nil }
        return value
    }

    var stringValue: String? {
        switch self {
        case .string(let string): return string
        default: return nil
        }
    }

    var doubleValue: Double? {
        switch self {
        case .number(let number): return number
        default: return nil
        }
    }

    var intValue: Int? {
        switch self {
        case .number(let number): return Int(number)
        default: return nil
        }
    }

    subscript(index: Int) -> JSON? {
        switch self {
        case .array(let array): return array[index]
        default: return nil
        }
    }

    var boolValue: Bool? {
        switch self {
        case .bool(let bool): return bool
        default: return nil
        }
    }
}

使用此功能,您可以执行以下操作:

With this, you can do things like:

let bilboJSON = """
{"value":"Bilbo"}
""".data(using: .utf8)!

let bilbo = try! JSONDecoder().decode(JSON.self, from: bilboJSON)
bilbo["value"]  // "Bilbo"

let javaJSON = """
{"value":["[Ljava.lang.Object;",["Bilbo",111]]}
""".data(using: .utf8)!

let java = try! JSONDecoder().decode(JSON.self, from: javaJSON)
java["value"]?[1]   // ["Bilbo", 111]
java["value"]?[1]?[0]?.stringValue  // "Bilbo" (as a String rather than a JSON.string)

?的泛滥有些难看,但是在我的实验中使用throws并不能真正使界面更好(特别是因为下标不能抛出).建议根据您的特定用例进行一些调整.

The proliferation of ? is somewhat ugly, but using throws on this doesn't really make the interface much nicer in my experiments (particularly because subscripts can't throw). Some tweaking may be advisable based on your particular use cases.

这篇关于如何处理完全动态的JSON响应的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-06 04:28