Swift Codable: Decode Dictionary with Unknown Keys

Swift Codable: decode dictionary with unknown keys

For your completely changed question, the solution is very similar. Your struct simply adds one additional layer above the array. There's no need for any custom decoding nor even any CodingKeys.

Note that you can't use Any in a Codable.

let json="""
{
"id": "123",
"data": [
{ "<id1>": { "<event>": "2019-05-21T16:15:34-0400" } },
{ "<id2>": { "<event>": "2019-07-01T12:15:34-0400" } },
]
}
"""
struct SampleModel: Codable {
let id: String
let data: [[String: [String: Date]]]
}

var decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
do {
let res = try decoder.decode(SampleModel.self, from: json.data(using: .utf8)!)
print(res)
} catch {
print(error)
}

The original answer for your original question.

Since you have an array of nested dictionary where none of the dictionary keys are fixed, and since there are no other fields, you can just decode this as a plain array.

Here's an example:

let json="""
[
{ "<id1>": { "<event>": "2019-07-01T12:15:34-0400" } },
{ "<id2>": { "<event>": "2019-05-21T17:15:34-0400" } },
]
"""
var decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
do {
let res = try decoder.decode([[String: [String: Date]]].self, from: json.data(using: .utf8)!)
print(res)
} catch {
print(error)
}

Swift 4.2 Decoding Object Unknown Keys

Your value for "data" is dictionary with key of type String and value as your custom model. If you're using Codable, just specify type of data as dictionary

let data: [String: YourModel]

Then decode received Data as your Response model

struct Response: Decodable {
let result, id: Int
let error: String?
let data: [String: YourModel]
}

struct YourModel: Decodable {
let knownKey: String
}

If you need to get all your models, just use compactMap on your dictionary

do {
let decoded = try JSONDecoder().decode(Response.self, from: data)
let models = decoded.data.compactMap { $0.value }
} catch { print(error) }

How to decode json with unknown key

This is done by creating the necessary coding keys for the brand number dynamically, like this:

struct AutoOrderModel: Decodable {
var brands: BrandList
let price: [Int]
let year: [Int]
let fuelType: [Int]
let engineCapacity: [String]
let color: [Int]
let gearboxId: [Int]
let isGeorgia: Bool

enum CodingKeys: String, CodingKey {
case brands, price, year, color
case fuelType = "fuel_type"
case engineCapacity = "engine_capacity"
case gearboxId = "gearbox_id"
case isGeorgia = "is_georgia"
}
}

struct BrandList: Decodable {
var any: Bool = false
let brands: [String: Models]

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

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

var brands = [String: Models]()
for key in container.allKeys {
if let model = try? container.decode(Models.self, forKey: key) {
brands[key.stringValue] = model
} else if let any = try? container.decode(Bool.self, forKey: key) {
self.any = any
}
}

self.brands = brands
}
}

struct Models: Decodable {
var isAll: Bool
var values: [Int]
var include: Bool

enum CodingKeys: String, CodingKey {
case isAll = "is_all"
case values, include
}
}

Swift decode JSON with unknown keys

You seem to be trying to decode your JSON as if it were an array of your Config structs - that would look like this:

[
{
"id": "1",
"animal": "cat"
},
{
"id": "2",
"animal": "dog"
},
{
"id": "3",
"animal": "elephant"
}
]

But your data (config.json) isn't that, it's just a JSON dictionary of String keys with String values.

You can instead "decode" it as just a String: String dictionary like:

let dict = Bundle.main.decode([String: String].self, from: "config.json")

and then dict["2"] would indeed be an optional string, with a value of .some("dog")

Or perhaps you mean your JSON to be an array of Config's, if you change the contents of config.json file to the above, and then decode it with:

let config = Bundle.main.decode([Config].self, from: "config.json")

Then the animal with id of 2 would be, e.g.

config.first(where: { $0.id == "2" })?.animal

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)

Using Swift 4 Codable Protocol with Unknown Dictionary Keys

You don't need to know the keys of the Dictionary compile time if you don't mind keeping a Dictionary after decoding.

You just need to specify the property with type Dictionary<String:YourCustomDecodableType>. The keys will be dates corresponding to observation and the value will an array of all objects with your custom type.

struct NearEarthObject: Codable {
let referenceID:String
let name:String
let imageURL:URL

private enum CodingKeys: String, CodingKey {
case referenceID = "neo_reference_id"
case name
case imageURL = "nasa_jpl_url"
}
}

struct NEOApiResponse: Codable {
let nearEarthObjects: [String:[NearEarthObject]]

private enum CodingKeys: String,CodingKey {
case nearEarthObjects = "near_earth_objects"
}
}

do {
let decodedResponse = try JSONDecoder().decode(NEOApiResponse.self, from: data)
} catch {
print(error)
}

How to use Swift Codable with a nested JSON structure and unknown keys

It's hard to tell if this solves your exact use case since I'm not sure what the purpose of the data is, and how you're anticipating using it, but both of the below solutions correctly decode your JSON into a more usable Swift object.

The simplest way is to model it exactly as the data structure you presented. For example it looks like someNumbers is an optional dictionary keyed by String, with Int values: [String: Int]?.

struct TopLevel: Decodable {
var someNumbers: [String: Int]?
var someNestedAny: [String: [String: Int]]?
var someNestedArray: [String: [String]]?
}

For a little more readability when passing objects around you can throw in some type aliases and it becomes

typealias SomeNumbers = [String: Int]
typealias SomeNestedAny = [String: [String: Int]]
typealias SomeNestedArray = [String: [String]]

struct TopLevel: Decodable {
var someNumbers: SomeNumbers?
var someNestedAny: SomeNestedAny?
var someNestedArray: SomeNestedArray?
}

To get useful things out you'll then need to call things like

topLevel.someNumbers?["22"]                 // 6
topLevel.someNestedAny?["8310"] // ["desktop": 2]
topLevel.someNestedAny?["8310"]?["desktop"] // 2
topLevel.someNestedArray?["52"] // ["browser"]
topLevel.someNestedArray?["52"]?[0] // "browser"

Or depending on your needs it may make more sense to loop through things

topLevel.someNestedAny?
.forEach { item in
print("|- \(item.key)")
item.value.forEach { any in
print("| |- \(any.key)")
print("| | |- \(any.value)")
}
}

// |- 61
// | |- browser
// | | |- 2
// |- 8310
// | |- desktop
// | | |- 2

Swift Json how to decode with no top level key and autogenerated keys

Since you need to have the id it should be a property of Room

struct Room: Decodable {
var id: Int?
let name: String
let description: String
}

With this we can decode the json as a dictionary of [String: Room] and use map to assign the right id to each room

do {
let dictionary = try JSONDecoder().decode([String: Room].self, from: data)
let rooms = dictionary.map { tuple -> Room in
var room = tuple.value
room.id = Int(tuple.key)
return room
}
print(rooms)
} catch {
print(error)
}

If you don't want to make id optional you can decode it as a dictionary of dictionaries and create Room object when mapping

do {
let dictionary = try JSONDecoder().decode([String: [String: String]].self, from: data)
let rooms = dictionary.compactMap { tuple -> Room? in
guard let id = Int(tuple.key), let name = tuple.value["name"], let description = tuple.value["description"] else {
return nil
}
return Room(id: id, name: name, description: description)
}
print(rooms)
} catch {
print(error)
}

Decoding the unknown objects back to original form in swift

Does this do what you need?

var arrayString : [String] = []
let encoder = JSONEncoder()

for item in RAWJSONArray {
if let data = try? encoder.encode(item),
let str = String(data: data, encoding: .utf8) {
arrayString.append(str)
}
}

or perhaps you literally want the strings only?

for case let .string(str) in RAWJSONArray {
arrayString.append(str)
}


Related Topics



Leave a reply



Submit