【问题标题】:JSON Parsing using Decodable使用可解码的 JSON 解析
【发布时间】:2018-02-19 12:57:00
【问题描述】:

我正在尝试使用可解码协议解析以下 JSON。我能够解析字符串值,例如 roomName。但我无法正确映射/解析 owners、admins、members 键。例如,使用下面的代码,我可以解析所有者/成员中的值是否以数组的形式出现。但在某些情况下,响应会以字符串值的形式出现(请参阅 JSON 中的 owners 键),但我无法映射字符串值。

注意:admins、members、owners 的值可以是字符串或数组(请参阅 JSON 中的 owner 和 members 键)

{
    "roomName": "6f9259d5-62d0-3476-6601-8c284a0b7dde",
    "owners": { 
        "owner": "anish@local.mac" //This can be array or string
    },
    "admins": null, //This can be array or string
    "members": {
        "member": [ //This can be array or string
            "steve@local.mac",
            "mahe@local.mac"
        ]
    }
}

型号:

 struct ChatRoom: Codable{
        var roomName: String! = ""
        var owners: Owners? = nil
        var members: Members? = nil
        var admins: Admins? = nil

        enum RoomKeys: String, CodingKey {
            case roomName
            case owners
            case members
            case admins
        }
       init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: RoomKeys.self)
            roomName = try container.decode(String.self, forKey: .roomName)
           if let member = try? container.decode(Members.self, forKey: .members) {
                members = member
            }
            if let owner = try? container.decode(Owners.self, forKey: .owners) {
                owners = owner
            }
            if let admin = try? container.decode(Admins.self, forKey: .admins) {
                admins = admin
            }
    }
}

//所有者模型

struct Owners:Codable{
    var owner: AnyObject?

    enum OwnerKeys:String,CodingKey {
        case owner
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: OwnerKeys.self)
        if let ownerValue = try container.decodeIfPresent([String].self, forKey: .owner){
            owner = ownerValue as AnyObject
        }
        else{
            owner = try? container.decode(String.self, forKey: .owner) as AnyObject
        }
    }

    func encode(to encoder: Encoder) throws {

    }
}

//成员模型

struct Members:Codable{
    var member:AnyObject?

    enum MemberKeys:String,CodingKey {
        case member
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: MemberKeys.self)
        if let memberValue = try container.decodeIfPresent([String].self, forKey: .member){
            member = memberValue as AnyObject
        }
        else{
            member = try? container.decode(String.self, forKey: .member) as AnyObject
        }
    }

    func encode(to encoder: Encoder) throws {

    }
}

【问题讨论】:

标签: json swift swift4 decodable


【解决方案1】:

当您获得ArrayString 的一些数据时,您可以在enum 的帮助下解析这个底层Type。这将减少一些样板代码以及您定义的每个 Type 能够具有 ArrayString 值的冗余代码。

您像这样定义enum

enum ArrayOrStringType: Codable {
    case array([String])
    case string(String)

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            self = try .array(container.decode([String].self))
        } catch DecodingError.typeMismatch {
            do {
                self = try .string(container.decode(String.self))
            } catch DecodingError.typeMismatch {
                throw DecodingError.typeMismatch(ArrayOrStringType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Encoded payload conflicts with expected type"))
            }
        }
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .array(let array):
            try container.encode(array)
        case .string(let string):
            try container.encode(string)
        }
    }
}

然后你的模型如下:

struct ChatRoom: Codable {
    let roomName: String
    let owners: Owner
    let admins: ArrayOrStringType?  // as you are likely to get null values also
    let members: Member

    struct Owner: Codable {
        let owner: ArrayOrStringType
    }
    struct Member: Codable {
        let member: ArrayOrStringType
    }
}
/// See!! No more customization inside every init(from:)

现在您可以解析包含任何所需类型的数据(ArrayString

测试数据1:

// owner having String type
let jsonTestData1 = """
{
    "roomName": "6f9259d5-62d0-3476-6601-8c284a0b7dde",
    "owners": {
        "owner": "anish@local.mac"
    },
    "admins": null,
    "members": {
        "member": [
            "steve@local.mac",
            "mahe@local.mac"
        ]
    }
}
""".data(using: .utf8)!

测试数据2:

// owner having [String] type
let jsonTestData2 = """
{
    "roomName": "6f9259d5-62d0-3476-6601-8c284a0b7dde",
    "owners": {
        "owner": ["anish1@local.mac", "anish2@local.mac"]
    },
    "admins": null,
    "members": {
        "member": [
            "steve@local.mac",
            "mahe@local.mac"
        ]
    }
}
""".data(using: .utf8)!

解码过程:

do {
    let chatRoom = try JSONDecoder().decode(ChatRoom.self, from:jsonTestData1)
    print(chatRoom)
} catch {
    print(error)
}
// will print
{
  "owners" : {
    "owner" : "anish@local.mac"
  },
  "members" : {
    "member" : [
      "steve@local.mac",
      "mahe@local.mac"
    ]
  },
  "roomName" : "6f9259d5-62d0-3476-6601-8c284a0b7dde"
}

do {
    let chatRoom = try JSONDecoder().decode(ChatRoom.self, from:jsonTestData2)
    print(chatRoom)
} catch {
    print(error)
}
// will print
{
  "owners" : {
    "owner" : [
      "anish1@local.mac",
      "anish2@local.mac"
    ]
  },
  "members" : {
    "member" : [
      "steve@local.mac",
      "mahe@local.mac"
    ]
  },
  "roomName" : "6f9259d5-62d0-3476-6601-8c284a0b7dde"
}


您甚至可以从结构中获得更多收益。假设您只想与所有者合作。您可能会尝试以 Swifty 方式获取值:

do {
    let chatRoom = try JSONDecoder().decode(ChatRoom.self, from:json)
    if case .array(let owners) = chatRoom.owners.owner {
        print(owners) // ["anish1@local.mac", "anish2@local.mac"]
    }
    if case .string(let owners) = chatRoom.owners.owner {
        print(owners) // "anish@local.mac"
    }
} catch {
    print(error)
}

希望这种结构比其他典型方式更有帮助。另外,这是明确考虑您的预期类型。它既不依赖于一种类型(仅Array)也不依赖于Any/AnyObject 类型。

【讨论】:

    【解决方案2】:

    这应该可行。为简单起见,我删除了 Admin 模型。我更喜欢 Owners/Members 是数组,因为它们可以有一个或多个值,这就是它们的用途,但是如果你希望它们是 AnyObject,你可以像你在 @ 987654322@.

    测试数据:

    var json = """
        {
            "roomName": "6f9259d5-62d0-3476-6601-8c284a0b7dde",
            "owners": {
                "owner": "anish@local.mac"
            },
            "admins": null,
            "members": {
                "member": [
                "steve@local.mac",
                "mahe@local.mac"
                ]
            }
        }
        """.data(using: .utf8)
    

    型号:

    struct ChatRoom: Codable, CustomStringConvertible {
        var roomName: String! = ""
        var owners: Owners? = nil
        var members: Members? = nil
    
        var description: String {
            let encoder = JSONEncoder()
            encoder.outputFormatting = .prettyPrinted
            let data = try? encoder.encode(self)
            return String(data: data!, encoding: .utf8)!
        }
    
        enum RoomKeys: String, CodingKey {
            case roomName
            case owners
            case members
            case admins
        }
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: RoomKeys.self)
            roomName = try container.decode(String.self, forKey: .roomName)
            members = try container.decode(Members.self, forKey: .members)
            owners = try? container.decode(Owners.self, forKey: .owners)
        }
    }
    
    struct Owners:Codable{
        var owner: [String]?
    
        enum OwnerKeys:String,CodingKey {
            case owner
        }
    
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: OwnerKeys.self)
            if let ownerValue = try? container.decode([String].self, forKey: .owner){
                owner = ownerValue
            }
            else if let own = try? container.decode(String.self, forKey: .owner) {
                owner = [own]
            }
        }
    }
    
    struct Members: Codable {
        var member:[String]?
    
        enum MemberKeys:String,CodingKey {
            case member
        }
    
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: MemberKeys.self)
            if let memberValue = try? container.decode([String].self, forKey: .member){
                member = memberValue
            }
            else if let str = try? container.decode(String.self, forKey: .member){
                member = [str]
            }
        }
    }
    

    测试:

    var decoder = JSONDecoder()
    try? print("\(decoder.decode(ChatRoom.self, from: json!))")
    

    输出:

    {
      "owners" : {
        "owner" : [
          "anish@local.mac"
        ]
      },
      "members" : {
        "member" : [
          "steve@local.mac",
          "mahe@local.mac"
        ]
      },
      "roomName" : "6f9259d5-62d0-3476-6601-8c284a0b7dde"
    }
    

    【讨论】:

    【解决方案3】:

    我重新创建了您的模型并使用您的 JSON 进行了测试,它运行良好。如果您的后端在不同的情况下(业务规则)返回不同的类型,也许最好的方法是为每种情况创建单独的变量。(imho)

    // Model
    import Foundation
    struct ChatRoom : Codable {
        let roomName : String?
        let owners : Owners?
        let admins : String?
        let members : Members?
    
        enum CodingKeys: String, CodingKey {
    
            case roomName = "roomName"
            case owners
            case admins = "admins"
            case members
        }
    
        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            roomName = try values.decodeIfPresent(String.self, forKey: .roomName)
            owners = try Owners(from: decoder)
            admins = try values.decodeIfPresent(String.self, forKey: .admins)
            members = try Members(from: decoder)
        }
    
    }
    

    -

    // Member Model
        import Foundation
        struct Members : Codable {
            let member : [String]?
    
            enum CodingKeys: String, CodingKey {
    
                case member = "member"
            }
    
            init(from decoder: Decoder) throws {
                let values = try decoder.container(keyedBy: CodingKeys.self)
                member = try values.decodeIfPresent([String].self, forKey: .member)
            }
    
        }
    

    -

    // Owner Model
    
    import Foundation
    struct Owners : Codable {
        let owner : String?
    
        enum CodingKeys: String, CodingKey {
    
            case owner = "owner"
        }
    
        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            owner = try values.decodeIfPresent(String.self, forKey: .owner)
        }
    
    }
    

    【讨论】:

    • 在上面的 Owner 模型中,您正在映射字符串值。但就我而言, Owners 可以是数组或字符串。所以我想相应地映射。
    猜你喜欢
    • 1970-01-01
    • 2020-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-15
    • 1970-01-01
    相关资源
    最近更新 更多