Swift 4使用Decodable解析多维和多类型数组的JSON

3
{
"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()

And the error that i got are

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

我知道数组中有一个整数,而我只对值进行字符串转换var values: [[String]]?(这就是为什么会出现此错误的原因),但由于它遵循Decodable协议,我不能在我的结构体中使用任何多维数组或元组。
我也不能将数据转换为字典,因为它会抛出错误“预计解码字典,但找到了数组”。
你有解决这个问题的想法吗?我尝试将数据强制转换为字符串类型,但仍然没有运气...
附言:如果所有的JSON格式都是字符串类型,那么就不会有问题,但我没有更改它的权限,因为我从API调用它。
5个回答

5
正如您所说,您的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]]?
}

Run online


谢谢,非常整洁,但在处理要用于JSONDecoder的JSON数组中的多种类型时,这是最佳实践吗? - Alan md noh
这是我第一次遇到多类型数组。老实说,我以为JSON无效。个人而言,我宁愿从后台发送一个对象数组,而不是一个多类型数组的数组。 - Orkhan Alikhanov
最好使用枚举而不是结构体——如果可以的话,使用解码器可能更好。 - JeremyP

2

这个答案是基于@Orkhan Alikhanov的答案。

由于这些值是IntString,我们可以用枚举代替Any来更好地表示它们。

以下代码可以复制到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
    }
}

正如您所看到的,我们明确表示每个值都将是IntString

响应

当然,我们需要我们的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

2
请注意,JSON中的内部数组具有一种类型的模式序列,我们知道这个序列是什么。内部数组中的类型按照一种模式序列排列:3个Ints、5个Strings、2个Ints和一个可能意味着日期的东西。显然,在JSON设计者的心目中,这11个元素都有固定和已知的含义。
这意味着我们可以逐个手动地捡起这11个元素,通过人工解码整个JSON表达式来获取它们。
数组具有混合类型,Swift不喜欢这样,因此我们必须将它们表示为Any(或AnyObject)的数组;但我们可以将它们作为它们自己获取,而不必将它们包装在人工中间结构中。
顺便说一句,如果您知道每个元素的含义,那么您可以将内部数组解码为具有11个命名属性的结构体,以表达每个元素的含义。这将是一个更清晰的结果,但我没有使用它,因为我不知道这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"]]

很好,所以只有在我们知道数组的类型和元素数量时才能使用,对吗? - Alan md noh
正确!但我们确实知道。这个JSON清楚地给出了每个数组元素的类型和含义。如果你知道它是什么,你可以解析内部数组成一个结构体,这将让你更加领先。 - matt
是的,谢谢你问这个问题;就像我说的,我一直想尝试这个。 - matt
1
我会辩论(并且已经修改了我的答案以便我进行争论)我的解决方案更好。另一个解决方案给您一个包含所需值的 value 属性的 IntegerOrStruct 数组。我的解决方案提供了原始 JSON 中显示的数组,完全不作改动。 - matt
@matt 我同意将多类型数组解析为结构体数组比解析为AnyIntegerOrString数组更好,就像你在最后一段中所解释的那样。 - Orkhan Alikhanov

1
解决方案
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"
    }
}

0

我已经在我的项目中尝试使用了你的解决方案,效果非常好。下面稍作修改,使其可以用于单个字符串、单个数组和多维数组。

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
}

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接