Swift 4 JSON 可使用多维和多类型数组解码
Swift 4 JSON Decodable with multidimensional and multitype array
{
"values":[
[1,1,7,"Azuan Child","Anak Azuan","12345","ACTIVE","Morning",7,12,"2017-11-09 19:45:00"],
[28,1,0,"Azuan Child2","Amran","123456","ACTIVE","Evening",1,29,"2017-11-09 19:45:00"]
]
}
好的,这是我从服务器收到的json格式
现在我想将它解码到我的结构中,但仍然没有成功。
struct ChildrenTable: Decodable {
var values: [[String]]?
}
我在 URLSession 上的调用方法如下所示
URLSession.shared.dataTask(with: request) { (data, response, err) in
guard let data = data else { return }
let dataAsString = String(data: data, encoding: .utf8)
print(dataAsString)
do {
let children = try
JSONDecoder().decode(ChildrenTable.self, from: data)
print (children)
} catch let jsonErr {
print ("Error serializing json: ", jsonErr)
}
}.resume()
我得到的错误是
Error serializing json:
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [Vito_Parent.ChildrenTable.(CodingKeys in _1B826CD7D9609504747BED0EC0B7D3B5).values, Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0)),
Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0))],
debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil))
我知道数组中有一个 int,我只为值 var values: [[String]]?
转换字符串(弹出此错误的原因),但我不能在我的结构中使用任何多维数组或元组,因为它遵循 Decodable 的协议。
我也无法将数据转换为字典,因为它会抛出错误 "Expected to decode Dictionary but found array instead"
有什么解决这个问题的想法吗?我尝试在数据上转换字符串类型,但仍然没有成功...
p/s:如果所有 json 格式都是字符串类型,就没有问题,但我没有权限更改它,因为我从 API 调用它.
正如您所说,您的 json 数组是多类型的,但您正试图将其全部解码为 String
。 String
与 Decodable
的默认一致性不允许这样做。我想到的唯一解决方案是引入新类型。
struct IntegerOrString: Decodable {
var value: Any
init(from decoder: Decoder) throws {
if let int = try? Int(from: decoder) {
value = int
return
}
value = try String(from: decoder)
}
}
struct ChildrenTable: Decodable {
var values: [[IntegerOrString]]?
}
观察到 JSON 中的内部数组有一个类型序列,我们知道这个序列是什么。内部数组中的类型按模式序列排列:3 个 Int、5 个字符串、2 个 Int,以及一些可能用作日期的类型。显然,在 JSON 设计者的心目中,这 11 个元素中的每一个都具有固定且已知的含义。
这意味着我们可以通过 dumpster-diving 和手动解码整个 JSON 表达式,手动地一个接一个地拾取 11 个元素。
数组有混合类型,Swift不喜欢这样,所以我们必须将它们表示为 Any(或 AnyObject)的数组;但我们可以 获取 它们作为 自身 ,而不必将它们包裹在人工中间结构中。
顺便说一句,如果您知道每个元素的含义,那么您可以将内部数组解码为具有 11 个命名属性的结构,而不是 Any 数组,这些属性表示每个元素的含义。那将是一个更清晰的结果,但我没有使用它,因为我不知道这 11 个值的含义。
开始吧:
struct S : Decodable {
var values : [[Any]]
enum CodingKeys : String, CodingKey {
case values
}
init(from decoder: Decoder) throws {
// get the dictionary
let con = try! decoder.container(keyedBy: CodingKeys.self)
// get the "values" array of array
var con2 = try! con.nestedUnkeyedContainer(forKey: CodingKeys.values)
var bigarr = [[Any]]()
for _ in 0..<con2.count! {
// get a nested array
var con3 = try! con2.nestedUnkeyedContainer()
// decode all the elements of the nested array
var arr = [Any]()
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(String.self))
bigarr.append(arr)
}
// all done! finish initialization
self.values = bigarr
}
}
let result = try! JSONDecoder().decode(S.self, from: jdata)
print(result.values)
// [[1, 1, 7, "Azuan Child", "Anak Azuan", "12345", "ACTIVE",
// "Morning", 7, 12, "2017-11-09 19:45:00"],
// [28, 1, 0, "Azuan Child2", "Amran", "123456", "ACTIVE",
// "Evening", 1, 29, "2017-11-09 19:45:00"]]
我已经尝试在我的项目中使用您的解决方案,它的效果非常棒。下面我做了一些修改,所以它可以用于单个字符串、单个数组和多维数组
struct TripModel: Decodable {
var tx_result: Any
var columns: [Any]
var values: [[Any]]
enum CodingKeys : String, CodingKey {
case tx_result
case columns
case values
}
init(from decoder: Decoder) throws {
var bigarr = [[Any]]()
var arrColumn = [Any]()
// get the dictionary
let con = try! decoder.container(keyedBy: CodingKeys.self)
let conResult = try! con.decode(String.self, forKey: CodingKeys.tx_result)
var conColumns = try! con.nestedUnkeyedContainer(forKey: CodingKeys.columns)
//print(String(describing: conColumns.count))
// get the "values" array of array
var con2 = try! con.nestedUnkeyedContainer(forKey: CodingKeys.values)
for _ in 0..<con2.count! {
// get a nested array
var con3 = try! con2.nestedUnkeyedContainer()
// decode all the elements of the nested array
var arr = [Any]()
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Double.self))
arr.append(try! con3.decode(String.self))
bigarr.append(arr)
}
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
// all done! finish initialization
self.tx_result = conResult
self.columns = arrColumn
self.values = bigarr
}
}
这个答案建立在 @Orkhan Alikhanov
的答案之上
由于值为 Int
或 String
,我们可以用枚举代替 Any
.
来更好地表示它们
The following code can be pasted into Playground
JSON
所以让我们从 JSON
开始
let data = """
{
"values": [
[1, 1, 7, "Azuan Child", "Anak Azuan", "12345", "ACTIVE", "Morning", 7, 12, "2017-11-09 19:45:00"],
[28, 1, 0, "Azuan Child2", "Amran", "123456", "ACTIVE", "Evening", 1, 29, "2017-11-09 19:45:00"]
]
}
""".data(using: .utf8)!
数据模型
现在我们可以定义我们的模型(它将是Decodable
)
enum IntOrString: Decodable {
case int(Int)
case string(String)
init(from decoder: Decoder) throws {
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
if let int = try? decoder.singleValueContainer().decode(Int.self) {
self = .int(int)
return
}
throw IntOrStringError.intOrStringNotFound
}
enum IntOrStringError: Error {
case intOrStringNotFound
}
}
As you can see we are explicitly saying that each value will be an Int
or a String
.
回应
当然我们需要 Response
类型。
struct Response: Decodable {
var values: [[IntOrString]]
}
解码
现在我们可以安全地解码 JSON
if let response = try? JSONDecoder().decode(Response.self, from: data) {
let values = response.values
for value in values {
for intOrString in value {
switch intOrString {
case .int(let int): print("It's an int: \(int)")
case .string(let string): print("It's a string: \(string)")
}
}
}
}
输出
It's an int: 1
It's an int: 1
It's an int: 7
It's a string: Azuan Child
It's a string: Anak Azuan
It's a string: 12345
It's a string: ACTIVE
It's a string: Morning
It's an int: 7
It's an int: 12
It's a string: 2017-11-09 19:45:00
It's an int: 28
It's an int: 1
It's an int: 0
It's a string: Azuan Child2
It's a string: Amran
It's a string: 123456
It's a string: ACTIVE
It's a string: Evening
It's an int: 1
It's an int: 29
It's a string: 2017-11-09 19:45:00
解决方案
public struct UncertainValue<T: Decodable, U: Decodable>: Decodable {
public var tValue: T?
public var uValue: U?
public var value: Any? {
return tValue ?? uValue
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
tValue = try? container.decode(T.self)
uValue = try? container.decode(U.self)
if tValue == nil && uValue == nil {
//Type mismatch
throw DecodingError.typeMismatch(type(of: self), DecodingError.Context(codingPath: [], debugDescription: "The value is not of type \(T.self) and not even \(U.self)"))
}
}
}
例子
{
"results": [{
"name": "Gala",
"age": 1,
"type": "Pug"
}, {
"name": "Keira",
"age": "7",
"type": "Collie Rough"
}]
}
用法
struct Dog: Decodable, CustomStringConvertible {
var name: String
var age: UncertainValue<Int, String>
var type: String
var description: String {
return "\(name) is a lovely \(type) of \(age.value!) years old"
}
}
{
"values":[
[1,1,7,"Azuan Child","Anak Azuan","12345","ACTIVE","Morning",7,12,"2017-11-09 19:45:00"],
[28,1,0,"Azuan Child2","Amran","123456","ACTIVE","Evening",1,29,"2017-11-09 19:45:00"]
]
}
好的,这是我从服务器收到的json格式
现在我想将它解码到我的结构中,但仍然没有成功。
struct ChildrenTable: Decodable {
var values: [[String]]?
}
我在 URLSession 上的调用方法如下所示
URLSession.shared.dataTask(with: request) { (data, response, err) in
guard let data = data else { return }
let dataAsString = String(data: data, encoding: .utf8)
print(dataAsString)
do {
let children = try
JSONDecoder().decode(ChildrenTable.self, from: data)
print (children)
} catch let jsonErr {
print ("Error serializing json: ", jsonErr)
}
}.resume()
我得到的错误是
Error serializing json:
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [Vito_Parent.ChildrenTable.(CodingKeys in _1B826CD7D9609504747BED0EC0B7D3B5).values, Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0)),
Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0))],
debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil))
我知道数组中有一个 int,我只为值 var values: [[String]]?
转换字符串(弹出此错误的原因),但我不能在我的结构中使用任何多维数组或元组,因为它遵循 Decodable 的协议。
我也无法将数据转换为字典,因为它会抛出错误 "Expected to decode Dictionary but found array instead"
有什么解决这个问题的想法吗?我尝试在数据上转换字符串类型,但仍然没有成功...
p/s:如果所有 json 格式都是字符串类型,就没有问题,但我没有权限更改它,因为我从 API 调用它.
正如您所说,您的 json 数组是多类型的,但您正试图将其全部解码为 String
。 String
与 Decodable
的默认一致性不允许这样做。我想到的唯一解决方案是引入新类型。
struct IntegerOrString: Decodable {
var value: Any
init(from decoder: Decoder) throws {
if let int = try? Int(from: decoder) {
value = int
return
}
value = try String(from: decoder)
}
}
struct ChildrenTable: Decodable {
var values: [[IntegerOrString]]?
}
观察到 JSON 中的内部数组有一个类型序列,我们知道这个序列是什么。内部数组中的类型按模式序列排列:3 个 Int、5 个字符串、2 个 Int,以及一些可能用作日期的类型。显然,在 JSON 设计者的心目中,这 11 个元素中的每一个都具有固定且已知的含义。
这意味着我们可以通过 dumpster-diving 和手动解码整个 JSON 表达式,手动地一个接一个地拾取 11 个元素。
数组有混合类型,Swift不喜欢这样,所以我们必须将它们表示为 Any(或 AnyObject)的数组;但我们可以 获取 它们作为 自身 ,而不必将它们包裹在人工中间结构中。
顺便说一句,如果您知道每个元素的含义,那么您可以将内部数组解码为具有 11 个命名属性的结构,而不是 Any 数组,这些属性表示每个元素的含义。那将是一个更清晰的结果,但我没有使用它,因为我不知道这 11 个值的含义。
开始吧:
struct S : Decodable {
var values : [[Any]]
enum CodingKeys : String, CodingKey {
case values
}
init(from decoder: Decoder) throws {
// get the dictionary
let con = try! decoder.container(keyedBy: CodingKeys.self)
// get the "values" array of array
var con2 = try! con.nestedUnkeyedContainer(forKey: CodingKeys.values)
var bigarr = [[Any]]()
for _ in 0..<con2.count! {
// get a nested array
var con3 = try! con2.nestedUnkeyedContainer()
// decode all the elements of the nested array
var arr = [Any]()
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(String.self))
bigarr.append(arr)
}
// all done! finish initialization
self.values = bigarr
}
}
let result = try! JSONDecoder().decode(S.self, from: jdata)
print(result.values)
// [[1, 1, 7, "Azuan Child", "Anak Azuan", "12345", "ACTIVE",
// "Morning", 7, 12, "2017-11-09 19:45:00"],
// [28, 1, 0, "Azuan Child2", "Amran", "123456", "ACTIVE",
// "Evening", 1, 29, "2017-11-09 19:45:00"]]
我已经尝试在我的项目中使用您的解决方案,它的效果非常棒。下面我做了一些修改,所以它可以用于单个字符串、单个数组和多维数组
struct TripModel: Decodable {
var tx_result: Any
var columns: [Any]
var values: [[Any]]
enum CodingKeys : String, CodingKey {
case tx_result
case columns
case values
}
init(from decoder: Decoder) throws {
var bigarr = [[Any]]()
var arrColumn = [Any]()
// get the dictionary
let con = try! decoder.container(keyedBy: CodingKeys.self)
let conResult = try! con.decode(String.self, forKey: CodingKeys.tx_result)
var conColumns = try! con.nestedUnkeyedContainer(forKey: CodingKeys.columns)
//print(String(describing: conColumns.count))
// get the "values" array of array
var con2 = try! con.nestedUnkeyedContainer(forKey: CodingKeys.values)
for _ in 0..<con2.count! {
// get a nested array
var con3 = try! con2.nestedUnkeyedContainer()
// decode all the elements of the nested array
var arr = [Any]()
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Double.self))
arr.append(try! con3.decode(String.self))
bigarr.append(arr)
}
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
// all done! finish initialization
self.tx_result = conResult
self.columns = arrColumn
self.values = bigarr
}
}
这个答案建立在 @Orkhan Alikhanov
的答案之上由于值为 Int
或 String
,我们可以用枚举代替 Any
.
The following code can be pasted into Playground
JSON
所以让我们从 JSON
开始let data = """
{
"values": [
[1, 1, 7, "Azuan Child", "Anak Azuan", "12345", "ACTIVE", "Morning", 7, 12, "2017-11-09 19:45:00"],
[28, 1, 0, "Azuan Child2", "Amran", "123456", "ACTIVE", "Evening", 1, 29, "2017-11-09 19:45:00"]
]
}
""".data(using: .utf8)!
数据模型
现在我们可以定义我们的模型(它将是Decodable
)
enum IntOrString: Decodable {
case int(Int)
case string(String)
init(from decoder: Decoder) throws {
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
if let int = try? decoder.singleValueContainer().decode(Int.self) {
self = .int(int)
return
}
throw IntOrStringError.intOrStringNotFound
}
enum IntOrStringError: Error {
case intOrStringNotFound
}
}
As you can see we are explicitly saying that each value will be an
Int
or aString
.
回应
当然我们需要 Response
类型。
struct Response: Decodable {
var values: [[IntOrString]]
}
解码
现在我们可以安全地解码 JSON
if let response = try? JSONDecoder().decode(Response.self, from: data) {
let values = response.values
for value in values {
for intOrString in value {
switch intOrString {
case .int(let int): print("It's an int: \(int)")
case .string(let string): print("It's a string: \(string)")
}
}
}
}
输出
It's an int: 1
It's an int: 1
It's an int: 7
It's a string: Azuan Child
It's a string: Anak Azuan
It's a string: 12345
It's a string: ACTIVE
It's a string: Morning
It's an int: 7
It's an int: 12
It's a string: 2017-11-09 19:45:00
It's an int: 28
It's an int: 1
It's an int: 0
It's a string: Azuan Child2
It's a string: Amran
It's a string: 123456
It's a string: ACTIVE
It's a string: Evening
It's an int: 1
It's an int: 29
It's a string: 2017-11-09 19:45:00
解决方案
public struct UncertainValue<T: Decodable, U: Decodable>: Decodable {
public var tValue: T?
public var uValue: U?
public var value: Any? {
return tValue ?? uValue
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
tValue = try? container.decode(T.self)
uValue = try? container.decode(U.self)
if tValue == nil && uValue == nil {
//Type mismatch
throw DecodingError.typeMismatch(type(of: self), DecodingError.Context(codingPath: [], debugDescription: "The value is not of type \(T.self) and not even \(U.self)"))
}
}
}
例子
{
"results": [{
"name": "Gala",
"age": 1,
"type": "Pug"
}, {
"name": "Keira",
"age": "7",
"type": "Collie Rough"
}]
}
用法
struct Dog: Decodable, CustomStringConvertible {
var name: String
var age: UncertainValue<Int, String>
var type: String
var description: String {
return "\(name) is a lovely \(type) of \(age.value!) years old"
}
}