-1

I'm just learning the Swift Decodable protocol and am running into a problem. I am able to decode one json object into a swift object, but am stuck with decoding an array.

What goes well:

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

I can decode it to a Fact object with following code:

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

But the api is giving me an array of objects:

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)

And I want to store that in an allFacts array that hold my Fact objects

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

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

I'm using the same extension on my Fact struct. But it's giving me an error and I am totally lost for a second. Any idea how I can solve this ? Do I need to create codingKeys for the class as well ?

Expected to decode Array<Any> but found a dictionary instead."
Bjorn Morrhaye
  • 687
  • 9
  • 30

1 Answers1

1

I recommend not to mess around with nested containers. This is less efficient than the default stuff. In your case you would have to use nestedUnkeyedContainer and iterate the array which is still more expensive.

Instead just add a 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)
vadian
  • 274,689
  • 30
  • 353
  • 361