簡體   English   中英

使用 Codable 解析嵌套的無鍵 JSON

[英]Parsing nested unkeyed JSON with Codable

我正在嘗試使用 Codable 解析一組異構對象。 這些對象也沒有鍵控。 我應該注意我的容器結構是正確的,因為它確實循環並在所有正確的時間打印“它是 type1”,如下所示。 我只是不知道如何訪問實際的 object。 這是我的代碼:

var data: [Any]

public init(from decoder: Decoder) throws {
    var container = try! decoder.container(keyedBy: CodingKeys.self).nestedUnkeyedContainer(forKey: .data)

    while !container.isAtEnd {
        var itemContainer = try container.nestedContainer(keyedBy: CodingKeys.self)

        let itemType = try itemContainer.decode(String.self, forKey: .type)
        switch itemType {
        case "type1":
            print("it is type1")
            // this does not compile, but is what I need
            //let objectOfItem1 = try itemContainer.decode(Type1.self)

            // this compiles, but doesn't work because there is no key with these objects
            //let objectOfItem1 = try itemContainer.decode(Type1, forKey: .type)
        default:
            print("test:: it is the default")
        }
    }
}

private enum CodingKeys: String, CodingKey {
    case data
    case type
}

這是我正在嘗試解碼的 JSON (為清楚起見,提交了許多屬性):

"contents" : {
      "data" : [
        {
          "type" : "type1",
          "id" : "6a406cdd7a9cace5"
        }, 
       {
          "type" : "type2",
          "id" : "ljhdgsouilghoipsu"
        }
     ]
 }

如何正確地將我的單個Type1對象從這個結構中取出?

我認為繞過異構數據的簡單方法是使用枚舉作為臨時類型來包裝各種 Item 類型(都需要 Codable):

為了讓自己對此進行測試,我稍微更改了您的 json,以便為我提供更多異構數據進行測試。 我用過:

let json = """
{
  "contents": {
    "data": [
      {
        "type": "type1",
        "id": "6a406cdd7a9cace5"
      },
      {
        "type": "type2",
        "dbl": 1.01
      },
      {
        "type": "type3",
        "int": 5
      }
    ]
  }
}

然后創建了這個 json 代表的三種最終類型

struct Item1: Codable {
   let type: String
   let id: String
}
struct Item2: Codable {
   let type: String
   let dbl: Double
}
struct Item3: Codable {
   let type: String
   let int: Int
}

為了允許以類型安全的方式解碼多種類型(根據Codable的要求),您需要使用可以表示(或包裝)可能選項的單一類型。 具有關聯值的枚舉很好地為此工作

enum Interim {
  case type1 (Item1)
  case type2 (Item2)
  case type3 (Item3)
  case unknown  //to handle unexpected json structures
}

到目前為止,一切都很好,但是從 JSON 創建Interim版本時會稍微復雜一些。 它需要一個CodingKey枚舉,它代表所有Item#類型的所有可能鍵,然后它需要解碼 JSON 將所有這些鍵鏈接到它們各自的類型和數據:

extension Interim: Decodable {
   private enum InterimKeys: String, CodingKey {
      case type
      case id
      case dbl
      case int
   }
   init(from decoder: Decoder) throws {
      let container = try decoder.container(keyedBy: InterimKeys.self)
      let type = try container.decode(String.self, forKey: .type)
      switch type {
      case "type1":
         let id = try container.decode(String.self, forKey: .id)
         let item = Item1(type: type, id: id)
         self = .type1(item)
      case "type2":
         let dbl = try container.decode(Double.self, forKey: .dbl)
         let item = Item2(type: type, dbl: dbl)
         self = .type2(item)
      case "type3":
         let int = try container.decode(Int.self, forKey: .int)
         let item = Item3(type: type, int: int)
         self = .type3(item)
      default: self = .unknown
      }
   }
}

這提供了解碼異構組件的機制,現在我們只需要處理更高級別的密鑰。 由於我們有一個可DecodableInterim類型,這很簡單:

struct DataArray: Decodable {
   var data: [Interim]
}

struct Contents: Decodable {
   var contents: DataArray
}

現在這意味着 json 可以像這樣解碼......

let data = Data(json.utf8)
let decoder = JSONDecoder()
do {
    let contents = try decoder.decode(Contents.self, from: data)
    print(contents)
} catch {
    print("Failed to decode JSON")
    print(error.localizedDescription)
}

這成功地將數據解碼為嵌套結構,其中主要組件是Interim類型數組及其關聯的Item#對象。 上面生成了以下 output,顯示了這些嵌套類型:

Contents(contents: testbed.DataArray(data: [testbed.Interim.type1(testbed.Item1(type: "type1", id: "6a406cdd7a9cace5")), testbed.Interim.type2(testbed.Item2(type: "type2", dbl: 1.01)), testbed.Interim.type3(testbed.Item3(type: "type3", int: 5))]))

我認為應該有一種更安全的方法來使用 Type Erasure 來提供更可擴展的解決方案,但我還沒有完全理解這一點。

我認為你需要使用這個結構:

struct A: Codable {
let contents: B?

enum CodingKeys: String, CodingKey {
    case contents
}

struct B: Codable {
    let data: [C]?

    enum CodingKeys: String, CodingKey {
        case data
    }

    struct C: Codable {
        let type : String?
        let id : String?
    }

}

}

extension A {

init(from decoder: Decoder) throws {

    let container = try decoder.container(keyedBy: CodingKeys.self)

    let contents = try container.decodeIfPresent(B.self, forKey: .contents)

    self.init(contents: contents)
}

}

我想在側衛的回答中添加一個改進,以改進更清潔的方法,以避免將所有可能的密鑰存儲在InterimCodingKey下。 這是一個更新的Interim

enum Interim: Decodable {

    case item1(Item1)
    case item2(Item2)
    case item3(Item3)
    case unknown

    init(from decoder: Decoder) throws {
        let typeContainer = try decoder.container(keyedBy: Key.self)

        // Fallback for any unsupported types
        guard let type = try? typeContainer.decode(ItemType.self, forKey: .type) else {
            self = .unknown
            return
        }

        // Let corresponding Decodable Item to be initialized from the same decoder.
        switch type {
            case .type1: self = .item1(try .init(from: decoder))
            case .type2: self = .item2(try .init(from: decoder))
            case .type3: self = .item3(try .init(from: decoder))
        }
    }

    /// These are values for Item.type
    private enum ItemType: String, Decodable {
        case type1
        case type2
        case type3
    }

    private enum Key: CodingKey {
        case type
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM