1

I'm trying to understand how could I parse this multiple container JSON to an object. I've tried this approach (Mark answer), but he explain how to solve it using one-level container. For some reason I can't mimic the behaviour for multiple containers.

 {
     "graphql": {
        "shortcode_media": {
          "id": "1657677004214306744",
          "shortcode": "BcBQHPchwe4"
        }
     }
  }

class Post: Decodable {


    enum CodingKeys: String, CodingKey {
        case graphql // The top level "user" key
        case shortcode_media
    }

    enum PostKeys: String, CodingKey {
        case id
    }

    required init(from decoder: Decoder) throws {

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

        let post = try values.nestedContainer(keyedBy: PostKeys.self, forKey: .shortcode_media)

        self.id = try post.decode(String.self, forKey: .id)

    }

    var id: String

}

I'm getting:

Swift.DecodingError.Context(codingPath: [], debugDescription: "Cannot get KeyedDecodingContainer<PostKeys> -- no value found for key \"shortcode_media\"", underlyingError: nil))

Any help will be much appreciated, thank you!

4

2 回答 2

5

As vadian notes, you haven't matched the JSON structure. There is no shortcode_media key at the top level like you've encoded in CodingKeys.

In order to decode this with a custom decoder, you will need to walk through each level and deal with it.

class Post: Decodable {

    enum CodingKeys: String, CodingKey {
        case graphql
    }

    enum GraphQLKeys: String, CodingKey {
        case shortcode_media
    }

    enum PostKeys: String, CodingKey {
        case id
    }

    required init(from decoder: Decoder) throws {
        // unload the top level
        let container = try decoder.container(keyedBy: CodingKeys.self)
        // Unload the graphql key
        let graphql = try container.nestedContainer(keyedBy: GraphQLKeys.self, forKey: .graphql)
        // unload the shortcode_media key
        let post = try graphql.nestedContainer(keyedBy: PostKeys.self, forKey: .shortcode_media)

        // Finally, unload the actual object
        self.id = try post.decode(String.self, forKey: .id)
    }

    var id: String

}
于 2018-01-23T21:43:04.493 回答
1

Please read the JSON.

Any opening { is quasi a separator. The indentation of the JSON indicates also the hierarchy.

For clarity I removed all coding keys and left the variable names – which should be camelCased – unchanged.

struct Root : Decodable {
    let graphql : Graph

    // to access the `Media` object declare a lazy instantiated property

    lazy var media : Media = {
        return graphql.shortcode_media
    }()
}

struct Graph : Decodable {
    let shortcode_media : Media
}

struct Media : Decodable {
    let id: String
    let shortcode : String
}

let jsonString = """
{
    "graphql": {
        "shortcode_media": {
            "id": "1657677004214306744",
            "shortcode": "BcBQHPchwe4"
        }
    }
}
"""

do {       
    let data = Data(jsonString.utf8) 
    var result = try decoder.decode(Root.self, from: data)
    print(result.media)
} catch {
    print("error: ", error)
}

Writing a custom initializer with nestedContainer is more effort than creating the actual hierarchy.

Please paste the entire code in a Playground and check it out.

于 2018-01-23T21:34:08.907 回答