JSON 使用 Decodable 解析
JSON Parsing using Decodable
我正在尝试使用可解码协议解析以下 JSON。我能够解析字符串值,例如 roomName。但我无法正确 map/parse owners, admins, members 密钥。例如,使用下面的代码,我可以解析 owners/members 中的值是否作为数组出现。但在某些情况下,响应将以字符串值的形式出现(请参阅 JSON 中的 owners 键),但我无法映射字符串值。
注意:admins、members、owners 的值可以是字符串或数组(请参阅 JSON 中的 owners 和 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 进行了测试,效果很好。如果您的后端 returns 不同情况下的不同类型(业务规则),也许最好的方法是为每种情况创建单独的变量。(恕我直言)
// 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)
}
}
这应该有效。为简单起见,我删除了 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"
}
当您获取一些数据作为 Array
或 String
时,您可以在 enum
的帮助下解析这个基础 Type
。这将减少一些样板代码以及您定义的每个 Type
能够具有 Array
或 String
值的冗余代码。
你这样定义一个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"
}
您甚至可以从结构中获得更多。比方说,您只想与 owners 合作。您可能会尝试以 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)
}
Hope this structuring helps a lot more than other typical ways. Plus this is having the explicit consideration of your expected types. Neither it relies on one type (Array
only) nor Any
/AnyObject
type.
我正在尝试使用可解码协议解析以下 JSON。我能够解析字符串值,例如 roomName。但我无法正确 map/parse owners, admins, members 密钥。例如,使用下面的代码,我可以解析 owners/members 中的值是否作为数组出现。但在某些情况下,响应将以字符串值的形式出现(请参阅 JSON 中的 owners 键),但我无法映射字符串值。
注意:admins、members、owners 的值可以是字符串或数组(请参阅 JSON 中的 owners 和 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 进行了测试,效果很好。如果您的后端 returns 不同情况下的不同类型(业务规则),也许最好的方法是为每种情况创建单独的变量。(恕我直言)
// 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)
}
}
这应该有效。为简单起见,我删除了 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"
}
当您获取一些数据作为 Array
或 String
时,您可以在 enum
的帮助下解析这个基础 Type
。这将减少一些样板代码以及您定义的每个 Type
能够具有 Array
或 String
值的冗余代码。
你这样定义一个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"
}
您甚至可以从结构中获得更多。比方说,您只想与 owners 合作。您可能会尝试以 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)
}
Hope this structuring helps a lot more than other typical ways. Plus this is having the explicit consideration of your expected types. Neither it relies on one type (
Array
only) norAny
/AnyObject
type.