【问题标题】:Swift 4 JSON Decodable with multidimensional and multitype arraySwift 4 JSON 可解码多维和多类型数组
【发布时间】:2017-11-10 03:33:16
【问题描述】:
{
"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]]? 转换 String (这个错误弹出的原因),但我根本不能在我的结构中使用任何多维数组或元组,因为它遵循协议可解码。

我也无法将数据转换为字典,因为它会抛出错误“预期解码字典但找到数组”

有解决这个问题的想法吗?我尝试在数据上转换字符串类型,但仍然没有运气......

p/s:如果所有的json格式都是string类型的,没有问题,但是我没有权限改变它,因为我是从API调用的。

【问题讨论】:

    标签: arrays json multidimensional-array swift4 decodable


    【解决方案1】:

    正如您所说,您的 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 数组中的多种类型时执行的最佳实践吗?
    • 第一次接触多类型数组。老实说,我认为json是无效的。就个人而言,我不会发送一个多类型数组的数组,而是从后端发送一个对象数组。
    • 使用枚举可能比使用结构更好——如果可以使用解码器的话。
    【解决方案2】:

    观察 JSON 中的内部数组具有类型的模式序列,并且我们知道该序列是什么。内部数组中的类型是按模式排列的:3 个整数、5 个字符串、2 个整数,还有一些可能用作日期的东西。显然,在 JSON 设计者的心目中,这 11 个元素中的每一个都具有固定且已知的含义。

    这意味着我们可以手动地通过垃圾箱潜水和手动解码整个 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"]]
    

    【讨论】:

    • 很好,所以只有在我们知道数组的类型和元素数量的情况下才能使用它,对吧?
    • 正确!但我们确实知道这一点。这个 JSON 清楚地为每个数组元素提供了一个 type 和一个 meaning。如果你知道它是什么,你可以将内部数组解析成一个 struct ,这会让你在以后遥遥领先。
    • 是的,感谢您提出这个问题;就像我说的,我一直想试试这个。
    • 我会争辩(并修改了我的答案,以便我确实争辩)我的解决方案更好。另一种解决方案为您提供了一个 IntegerOrStruct 数组,其中嵌入了所需的值作为其value 属性。我的解决方案为您提供了与原始 JSON 中完全相同的数组。
    • @matt 我同意将多类型数组解析为结构数组比将其解析为Any 或IntegerOrString 数组更好,正如您在上一段中解释的那样
    【解决方案3】:

    此答案建立在 @Orkhan Alikhanov

    的答案之上

    由于值是Int 或String,我们可以用枚举代替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
        }
    }
    

    如您所见,我们明确表示每个值都是Int 或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
    

    【讨论】:

      【解决方案4】:

      解决方案

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

      【讨论】:

        【解决方案5】:

        我已经尝试在我的项目中使用您的解决方案,它的工作就像一个魅力。下面我做了一些修改,使其可以用于单字符串、单数组和多维数组

        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
        }
        

        }

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-10-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-07-02
          相关资源
          最近更新 更多