Swift 4从自定义JSON对象解码为不同的模型

时间:2019-01-22 13:43:00

标签: json swift

我有一个websocket,可以生成不同的json对象。对象不能包含任何公共字段

{
    "type": "apple",
    "kind": "fruit",
    "eatable": true
}
{
    "item": "key",
    "active": true 
}
{
    "tool": "screwdriver",
    "original": "toolBox",
    "cross-head": true
}

我为他们提供了一个类列表(它们可能包含一些逻辑),所以我需要解析它以映射具有某种层次结构的某些模型,例如 如果失败,则尝试解析水果;如果失败,则尝试解析键;尝试解析工具箱。有时我需要添加一些新类来解析现有类的某些对象和一些新字段。
如何组织挑选类进行解析?

更新

  1. 我无法控制后端数据,因此无法向我拥有的JSON添加任何字段。
  2. 对象一次出现一个。我对大多数人都有单独的课堂模型。问题是选择正确的类来映射JSON字段。

3 个答案:

答案 0 :(得分:4)

如果该对象中不存在该键,请尝试查找您要查找的键。这应该使您确定适合给定对象的模型类。

使用其他模型类中没有的唯一键

示例:

var array = NSArray(array: [[
    "type": "apple",
    "kind": "fruit",
    "eatable": true
    ],
    [
        "item": "key",
        "active": true
    ],
    [
    "tool": "screwdriver",
    "original": "toolBox",
    "cross-head": true
    ]])


for model in array as! [NSDictionary]
    {
        if(model.value(forKey: "type") != nil)
        {
            print("use Fruit Model Class")
        }
        else if(model.value(forKey: "item") != nil)
        {
            print("use second model class")
        }
        else
        {
            print("use third model class")
        }
    }

答案 1 :(得分:3)

您可以这样做:

首先,您声明您的类型符合Decodable协议:

struct Fruit : Decodable {
    let type : String
    let kind : String
    let eatable : Bool
}

struct Tool : Decodable {
    let tool : String
    let original : String
    let crossHead : Bool

    enum CodingKeys: String, CodingKey {
        case tool = "tool"
        case original = "original"
        case crossHead = "cross-head"
    }
}

然后,您扩展Decodable以“逆向”通用性的使用:

extension Decodable {
    static func decode(data : Data, decoder : JSONDecoder = JSONDecoder()) -> Self? {
        return try? decoder.decode(Self.self, from: data)
    }
}

然后,您扩展JSONDecoder以便在要测试的类型中尝试可解码的类型:

extension JSONDecoder {
    func decode(possibleTypes : [Decodable.Type], from data: Data) -> Any? {
        for type in possibleTypes {
            if let value = type.decode(data: data, decoder: self) {
                return value
            }
        }        
        return nil
    }
}

最后,您指定要尝试解码的类型:

let decodableTypes : [Decodable.Type] = [Fruit.self, Tool.self]

然后您可以使用它来解码JSON:

let jsonString = """
    {
        "tool": "screwdriver",
        "original": "toolBox",
        "cross-head": true
    }
    """
let jsonData = jsonString.data(using: .utf8)!

let myUnknownObject = JSONDecoder().decode(possibleTypes: decodableTypes, from: jsonData)

瞧瞧!

现在,您可以在decodableTypes中添加任意数量的类型,只要它们符合Decodable协议即可。

这不是最佳方法,因为如果您有很多类型,它并不是最佳选择,但是通过这种方式,您无需在数据中添加区分字段。

答案 2 :(得分:0)

如果所有这些字段都相关或具有联合样式,则可以考虑用户Enum,这也非常易于实现。

    let  data1 = """
    [{
        "type": "apple",
        "kind": "fruit",
        "eatable": true
    },
    {
        "item": "key",
        "active": true
    },
    {
        "tool": "screwdriver",
        "original": "toolBox",
        "cross-head": true
    }]
    """.data(using: .utf8)!

    struct JSONType : Decodable{
        let type: String
        let kind: String
        let eatable : Bool
    }

    struct JSONItem : Decodable{
        let item: String
        let active : Bool
    }

    struct JSONTool : Decodable{
        let tool: String
        let original : String
        let crosshead : Bool

        enum CodingKeys: String, CodingKey {
            case tool = "tool"
            case original = "original"
            case crosshead = "cross-head"
        }
    }

    enum JSONData : Decodable{

        case type(JSONType)
        case item(JSONItem)
        case tool(JSONTool)

        init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
            do{ let temp = try container.decode(JSONType.self); self = .type(temp) ; return}
            catch{do { let temp = try container.decode(JSONItem.self) ; self = .item(temp) ; return}
            catch{ let temp = try container.decode(JSONTool.self)  ; self = .tool(temp) ; return}}
            try  self.init(from: decoder)
        }

        func getValue()-> Any{
            switch self {
            case let .type(x): return x
            case let .item(x): return x
            case let .tool(x): return x
            }
        }
    }


    let result = try JSONDecoder().decode([JSONData].self, from: data1)
    print(result[0].getValue())
    print (result[1].getValue())
    print (result[2].getValue())