1

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

注意:管理员、成员、所有者的值可以是字符串或数组(参见 JSON 中的所有者和成员键)

{
    "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 {

    }
}
4

3 回答 3

2

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

测试数据:

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"
}
于 2018-02-19T13:38:26.390 回答
2

当您获取一些数据时,Array或者String您可以Typeenum. 这将减少一些样板代码以及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:)

现在您可以解析包含任何所需类型的数据 ( Array, String)

测试数据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类型。

于 2018-02-20T05:13:20.370 回答
0

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

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

}
于 2018-02-19T13:08:13.827 回答