Decode Dictionary with Random Initial Key

Decode Dictionary with Random Initial Key

You need

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let eventData = try decoder.decode([String:Event].self, from: data)

struct Event: Codable {
let eventTitle, description, timeStart, timeEnd: String
}

{} means a dictionary and [] means an array

Using KeyedDecodingContainer to decode an object with a random key

You just need to pass the define the dictionary value struct as decodable and use a String as the key for your dictionaries:


let json = """
{
"blocks": {
"F143CCC051927EEF59EEA78D16D80F855052BBF159EA6602843904C9445": {
"amount": "10000000000000000000000000000000",
"source": "6xswkroybxydyzaxybb1h531sx34omiu7an9t9jy19f9mca7a36s7by5e"
}
}
}
"""
let data = Data(json.utf8)

struct Root: Decodable {
let blocks: [String:Block]
}
struct Block: Decodable {
let amount: String
let source: String
}

do {
let blocks = try JSONDecoder().decode(Root.self, from: data).blocks
for (key, value) in blocks {
print(key, value) // "F143CCC051927EEF59EEA78D16D80F855052BBF159EA6602843904C9445 Block(amount: "10000000000000000000000000000000", source: "6xswkroybxydyzaxybb1h531sx34omiu7an9t9jy19f9mca7a36s7by5e")\n"
}
} catch {
print(error)
}

Swift 4 decoding nested JSON with random key

This solution works:

//: Playground - noun: a place where people can play
import Foundation
var str = """
{
"batchcomplete": "",
"query": {
"pages": {
"RANDOM ID": {
"pageid": 21721040,
"ns": 0,
"title": "Stack Overflow",
"extract": "Stack Overflow is a privately held website, the flagship site of the Stack Exchange Network...."
}
}
}
}
"""
struct Content: Decodable {
let batchcomplete: String
let query: Query
struct Query: Decodable {
let pages: Pages
struct Pages: Decodable {
var randomId: RandomID?
struct RandomID: Decodable {
let pageid: Int64
let ns: Int64
let title: String
let extract: String
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
for key in container.allKeys {
randomId = try? container.decode(RandomID.self, forKey: key)
}
print(container.allKeys)
}
struct CodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
}
}
}
let data = str.data(using: .utf8)!
var content = try? JSONDecoder().decode(Content.self, from: data)
print(content)

Swift 4 decodable nested json with random key attributes

A possible solution is to write a custom initializer to decode the dictionaries as [String:Dog] and map the values to an array

struct Dog : Decodable {
let name : String
let breed : String
}

struct DogResponse : Decodable {
let dogs : [Dog]

private enum CodingKeys: String, CodingKey {
case data = "Data"
}

init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let data = try values.decode([String : Dog].self, forKey: .data)
dogs = Array(data.values)
}
}

let dogResponse = try JSONDecoder().decode(DogResponse.self, from: data)
print(dogResponse.dogs)

===========================================================================

Or if you want to keep the dictionary structure it's still shorter

struct Dog : Decodable {
let name : String
let breed : String
}

struct DogResponse : Decodable {
let dogs : [String : Dog]

private enum CodingKeys: String, CodingKey {
case dogs = "Data"
}
}

Use swift Codable to decode JSON with values as keys

First I'm going to make some slight simplifications so I can focus on the important points of this question. I'm going to make everything immutable, replace the classes with structs, and only implement Decodable. Making this Encodable is a separate issue.

The central tool for handling unknown value keys is a CodingKey that can handle any string:

struct TitleKey: CodingKey {
let stringValue: String
init?(stringValue: String) { self.stringValue = stringValue }
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}

The second important tool is the ability to know your own title. That means asking the decoder "where are we?" That's the last element in the current coding path.

extension Decoder {
func currentTitle() throws -> String {
guard let titleKey = codingPath.last as? TitleKey else {
throw DecodingError.dataCorrupted(.init(codingPath: codingPath,
debugDescription: "Not in titled container"))
}
return titleKey.stringValue
}
}

And then we need a way to decode elements that are "titled" this way:

extension Decoder {
func decodeTitledElements<Element: Decodable>(_ type: Element.Type) throws -> [Element] {
let titles = try container(keyedBy: TitleKey.self)
return try titles.allKeys.map { title in
return try titles.decode(Element.self, forKey: title)
}
}
}

With that, we can invent a protocol for these "titled" things and decode them:

protocol TitleDecodable: Decodable {
associatedtype Element: Decodable
init(title: String, elements: [Element])
}

extension TitleDecodable {
init(from decoder: Decoder) throws {
self.init(title: try decoder.currentTitle(),
elements: try decoder.decodeTitledElements(Element.self))
}
}

And that's most of the work. We can use this protocol to make decoding pretty easy for the upper-level layers. Just implement init(title:elements:).

struct Drawer: TitleDecodable {
let title: String
let tools: [Tool]
init(title: String, elements: [Tool]) {
self.title = title
self.tools = elements
}
}

struct Container: TitleDecodable {
let title: String
let drawers: [Drawer]

init(title: String, elements: [Drawer]) {
self.title = title
self.drawers = elements
}
}

Tool is a little different since it's a leaf node and has other things to decode.

struct Tool: Decodable {
let title: String
let partNumber: String

enum CodingKeys: String, CodingKey {
case partNumber = "Partnumber"
}

init(from decoder: Decoder) throws {
self.title = try decoder.currentTitle()
let container = try decoder.container(keyedBy: CodingKeys.self)
self.partNumber = try container.decode(String.self, forKey: .partNumber)
}
}

That just leaves the very top level. We'll create a Containers type just to wrap things up.

struct Containers: Decodable {
let containers: [Container]
init(from decoder: Decoder) throws {
self.containers = try decoder.decodeTitledElements(Container.self)
}
}

And to use it, decode the top level Containers:

let containers = try JSONDecoder().decode(Containers.self, from: json)
print(containers.containers)

Note that since JSON objects are not order-preserving, the arrays may not be in the same order as the JSON, and may not be in the same order between runs.

Gist

Swift 4 Decodable with keys not known until decoding time

The key is in how you define the CodingKeys property. While it's most commonly an enum it can be anything that conforms to the CodingKey protocol. And to make dynamic keys, you can call a static function:

struct Category: Decodable {
struct Detail: Decodable {
var category: String
var trailerPrice: String
var isFavorite: Bool?
var isWatchlist: Bool?
}

var name: String
var detail: Detail

private struct CodingKeys: CodingKey {
var intValue: Int?
var stringValue: String

init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
init?(stringValue: String) { self.stringValue = stringValue }

static let name = CodingKeys.make(key: "categoryName")
static func make(key: String) -> CodingKeys {
return CodingKeys(stringValue: key)!
}
}

init(from coder: Decoder) throws {
let container = try coder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.detail = try container.decode([Detail].self, forKey: .make(key: name)).first!
}
}

Usage:

let jsonData = """
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
}
]
""".data(using: .utf8)!

let categories = try! JSONDecoder().decode([Category].self, from: jsonData)

(I changed isFavourit in the JSON to isFavourite since I thought it was a mispelling. It's easy enough to adapt the code if that's not the case)

Decoding list containing dictionaries

Your data is a list of dictionaries.

For this reason you need to cycle through its content in order to access data.
Please try this print statement to look at the data more closely:

for item in data_list:
print(item)

In order to access duration per each item you can use similar code:

for item in data_list:
print(item['duration'])

You can also use list comprehension to achieve the same result:

duration = [item['duration'] for item in data_list]

List comprehension is a Pythonic way to obtain the same result, you can read more about it here.

The same principle can be applied twice if a key in your data contains a list or another iterable, here's another example:

for item in data:
print("\nPrinting waypoints for name: " + item['name'])
for way_point in item['way_points']:
print(way_point)


Related Topics



Leave a reply



Submit