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
Uimarkuptextprintformatter and MAC Catalyst
Table View's 'Cellforrow(At:)' Is 'Nil' in Unit Test
How to Initialize UIbezierpath to Draw a Circle in Swift
How to Byte Reverse Nsdata Output in Swift The Littleendian Way
How to Compare Two Dates (Nsdate) in Swift 3 and Get The Days Between Them
Convert Single File to Swift 3 in Xcode 8
Swift Error "Domain=Nscocoaerrordomain Code=3840 "Invalid Value Around Character 1."
Uibutton Action Is Not Triggered After Constraint Layouts Changed
Why Is Inceptionv3 Machine Learning Model Not Recognized on My Project
Xcodebuild Commands Failed to Generate Ipa
Uisearchcontroller Searchbar Misaligns While Active During Rotation
Swift Cannot Invoke '*' with an Argument List of Type '(Int, Int)'
What Is The Default Value of The Padding Modifier in Swift
How Are Swift Enums Implemented Internally
Swift Playground with Debugger Support
Get The Start Point and End Point of The UIpangesturerecognizer in Swift