Декодировать массив JSON - PullRequest
0 голосов
/ 24 апреля 2020

Я только что изучил протокол Swift Decodable и столкнулся с проблемой. Я могу декодировать один json объект в быстрый объект, но я застрял с декодированием массива.

Что идет хорошо:

представьте себе следующее json:

let json = """
{
  "all" : {
    "_id": "59951d5ef2db18002031693c",
    "text": "America’s cats, including housecats that adventure outdoors and feral cats, kill between 1.3 billion and 4.0 billion birds in a year.",
    "type": "cat",
    "user": {
      "_id": "5a9ac18c7478810ea6c06381",
      "name": {
        "first": "Alex",
        "last": "Wohlbruck"
      }
    },
    "upvotes": 4,
    "userUpvoted": null
  }
}
"""

let jsonData = json.data(using: .utf8)

Я могу декодировать его в объект Fact с помощью следующего кода:

enum Type: String, Decodable {
    case cat = "cat"
}

struct Fact {
    let id: String
    let text: String
    let type: Type
    let upvotes: Int

    enum CodingKeys: CodingKey {
        case all
    }

    enum FactKeys: CodingKey {
        case _id, text, type, upvotes
    }
}

extension Fact: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let allContainer = try container.nestedContainer(keyedBy: FactKeys.self, forKey: .all)
        id = try allContainer.decode(String.self, forKey: ._id)
        text = try allContainer.decode(String.self, forKey: .text)
        type = try allContainer.decode(Type.self, forKey: .type)
        upvotes = try allContainer.decode(Int.self, forKey: .upvotes)
    }
}

let decoder = JSONDecoder()
let fact = try decoder.decode(Fact.self, from: jsonData!)

Но API дает мне массив объектов:

let json = """
{
  "all": [
    {
      "_id": "59951d5ef2db18002031693c",
      "text": "America’s cats, including housecats that adventure outdoors and feral cats, kill between 1.3 billion and 4.0 billion birds in a year.",
      "type": "cat",
      "user": {
        "_id": "5a9ac18c7478810ea6c06381",
        "name": {
          "first": "Alex",
          "last": "Wohlbruck"
        }
      },
      "upvotes": 4,
      "userUpvoted": null
    },
    {
      "_id": "5b01a447c6914f0014cc9a30",
      "text": "The special sensory organ called the Jacobson's organ allows a cat to have 14 times the sense of smell of a human. It consists of two fluid-filled sacs that connect to the cat's nasal cavity and is located on the roof of their mouth behind their teeth.",
      "type": "cat",
      "user": {
        "_id": "5a9ac18c7478810ea6c06381",
        "name": {
          "first": "Alex",
          "last": "Wohlbruck"
        }
      },
      "upvotes": 4,
      "userUpvoted": null
    }
  ]
}
"""

let jsonData = json.data(using: .utf8)

И я хочу сохранить это в массиве allFacts, который содержит мои объекты Fact

class Facts: ObservableObject {
    @Published var allFacts = [Fact]()
}

let decoder = JSONDecoder()
let allFacts = try decoder.decode([Fact].self, from: jsonData!)

Я использую то же самое расширение на моей структуре Fact. Но это дает мне ошибку, и я совершенно потерян на секунду. Любая идея, как я могу решить это? Нужно ли создавать codingKeys для класса?

Expected to decode Array<Any> but found a dictionary instead."

1 Ответ

1 голос
/ 24 апреля 2020

Рекомендую не возиться с вложенными контейнерами. Это менее эффективно, чем материал по умолчанию. В вашем случае вам придется использовать nestedUnkeyedContainer и итерировать массив, который еще дороже.

Вместо этого просто добавьте root struct

let json = """
{
  "all": [
    {
      "_id": "59951d5ef2db18002031693c",
      "text": "America’s cats, including housecats that adventure outdoors and feral cats, kill between 1.3 billion and 4.0 billion birds in a year.",
      "type": "cat",
      "user": {
        "_id": "5a9ac18c7478810ea6c06381",
        "name": {
          "first": "Alex",
          "last": "Wohlbruck"
        }
      },
      "upvotes": 4,
      "userUpvoted": null
    },
    {
      "_id": "5b01a447c6914f0014cc9a30",
      "text": "The special sensory organ called the Jacobson's organ allows a cat to have 14 times the sense of smell of a human. It consists of two fluid-filled sacs that connect to the cat's nasal cavity and is located on the roof of their mouth behind their teeth.",
      "type": "cat",
      "user": {
        "_id": "5a9ac18c7478810ea6c06381",
        "name": {
          "first": "Alex",
          "last": "Wohlbruck"
        }
      },
      "upvotes": 4,
      "userUpvoted": null
    }
  ]
}
"""

let jsonData = Data(json.utf8)

enum Type: String, Decodable {
    case cat
}

struct Root : Decodable {
    let all : [Fact]
}

struct Fact : Decodable {
    let id: String
    let text: String
    let type: Type
    let upvotes: Int

    enum CodingKeys : String, CodingKey {
        case id = "_id", text, type, upvotes
    }
}

let decoder = JSONDecoder()
let root = try decoder.decode(Root.self, from: jsonData)

print(root.all)
...