-
-
Save loudmouth/332e8d89d8de2c1eaf81875cfcd22e24 to your computer and use it in GitHub Desktop.
import Foundation | |
// Inspired by https://gist.github.com/mbuchetics/c9bc6c22033014aa0c550d3b4324411a | |
struct JSONCodingKeys: CodingKey { | |
var stringValue: String | |
init?(stringValue: String) { | |
self.stringValue = stringValue | |
} | |
var intValue: Int? | |
init?(intValue: Int) { | |
self.init(stringValue: "\(intValue)") | |
self.intValue = intValue | |
} | |
} | |
extension KeyedDecodingContainer { | |
func decode(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any> { | |
let container = try self.nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key) | |
return try container.decode(type) | |
} | |
func decodeIfPresent(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any>? { | |
guard contains(key) else { | |
return nil | |
} | |
return try decode(type, forKey: key) | |
} | |
func decode(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any> { | |
var container = try self.nestedUnkeyedContainer(forKey: key) | |
return try container.decode(type) | |
} | |
func decodeIfPresent(_ type: Array<Any>.Type, forKey key: K) throws -> Array<Any>? { | |
guard contains(key) else { | |
return nil | |
} | |
return try decode(type, forKey: key) | |
} | |
func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> { | |
var dictionary = Dictionary<String, Any>() | |
for key in allKeys { | |
if let if let boolValue = try? decode(Bool.self, forKey: key) { | |
dictionary[key.stringValue] = boolValue | |
} else if let stringValue = try? decode(String.self, forKey: key) { | |
dictionary[key.stringValue] = stringValue | |
} else intValue = try? decode(Int.self, forKey: key) { | |
dictionary[key.stringValue] = intValue | |
} else if let doubleValue = try? decode(Double.self, forKey: key) { | |
dictionary[key.stringValue] = doubleValue | |
} else if let fileMetaData = try? decode(Asset.FileMetadata.self, forKey: key) { | |
dictionary[key.stringValue] = fileMetaData // Custom contentful type. | |
} else if let nestedDictionary = try? decode(Dictionary<String, Any>.self, forKey: key) { | |
dictionary[key.stringValue] = nestedDictionary | |
} else if let nestedArray = try? decode(Array<Any>.self, forKey: key) { | |
dictionary[key.stringValue] = nestedArray | |
} | |
} | |
return dictionary | |
} | |
} | |
extension UnkeyedDecodingContainer { | |
mutating func decode(_ type: Array<Any>.Type) throws -> Array<Any> { | |
var array: [Any] = [] | |
while isAtEnd == false { | |
if let value = try? decode(Bool.self) { | |
array.append(value) | |
} else if let value = try? decode(Double.self) { | |
array.append(value) | |
} else if let value = try? decode(String.self) { | |
array.append(value) | |
} else if let nestedDictionary = try? decode(Dictionary<String, Any>.self) { | |
array.append(nestedDictionary) | |
} else if let nestedArray = try? decode(Array<Any>.self) { | |
array.append(nestedArray) | |
} | |
} | |
return array | |
} | |
mutating func decode(_ type: Dictionary<String, Any>.Type) throws -> Dictionary<String, Any> { | |
let nestedContainer = try self.nestedContainer(keyedBy: JSONCodingKeys.self) | |
return try nestedContainer.decode(type) | |
} | |
} |
@hashemp206 done, good catch ;-)
@loudmouth, did you have a license in mind for this gist? Looks just like the kind of thing I've been looking for, to work with my Codable stuff for JSON parsing...
Good work!
Correcting the above link from @sakrist: https://github.com/3D4Medical/glTFSceneKit/blob/master/Sources/glTFSceneKit/GLTF/JSONCodingKeys.swift
@loudmouth
Few mistakes:
if let if let boolValue
->if let boolValue
} else intValue
->} else if let intValue
One more check to see if the value of the key is null then return nil
func decodeIfPresent(_ type: Dictionary<String, Any>.Type, forKey key: K) throws -> Dictionary<String, Any>? {
guard contains(key) else {
return nil
}
guard try decodeNil(forKey: key) else {
return nil
}
return try decode(type, forKey: key)
}
dude! you save my time
Thanks for this code!
One of our API's returned an array where one of the values was 'null', which got the [Any] decoder into an infinite loop.
Fixed it this way:
mutating func decode(_ type: Array<Any>.Type) throws -> Array<Any> {
var array: [Any] = []
while isAtEnd == false {
// When one of the values is `null`, we seem to enter an infinite loop.. Check if the value is null and continue if so
do {
let value: String? = try decode(String?.self)
if value == nil {
// Found a nil value... skip the rest of this function and continue with the next value
continue
}
} catch {
// if we fail, it isn't a String? and isn't nil
// continue with testing values
}
if let value = try? decode(Bool.self) {
array.append(value)
} else if let value = try? decode(Int.self) {
array.append(value)
} else if let value = try? decode(Double.self) {
array.append(value)
} else if let value = try? decode(String.self) {
array.append(value)
} else if let nestedDictionary = try? decode(JSON.self) {
array.append(nestedDictionary)
} else if let nestedArray = try? decode(Array<Any>.self) {
array.append(nestedArray)
}
}
return array
}
@ChiellieNL thanks for sharing that fix! I had to replace the line with JSON.self
to the original line to get it to compile on my end.
Here is my version with decoding (original post), encoding (thank you @sakrist!) and the null fix from above. I also changed the types to the modern syntax of [Any]
instead of Array<Any>
, and [String: Any]
instead of Dictionary<String, Any>
.
https://gist.github.com/mikebuss/17142624da4baf9cdcc337861e256533
@converted2mac I guess Github doesn't give notifications for comments on Gists 🤔
Anyway, no license, i'd say go ahead and use the code as you need ;-)
@mikebuss null
value should be appended to the array as well. There were a couple of bugs including 2 infinite loops for encoding/decoding [Any]. I've created a new version that works well with all cases and fixed all the bugs that I found.
https://gist.github.com/sukov/d3834c0e7b72e4f7575f753b352f6ddd
Thanks for sharing this! I really need it :)
Thanks for this. I think if you renamed the gist to end in .swift
instead of [space]Swift
syntax highlighting will be added.
Thanks for sharing this 👍
Is this supposed to be able to decode a struct with an [Any] parameter automatically? Or we need to manually decode it?
I'm pondering... would this GIST code help me to decode the JSON from Alpha Vantage's TimeSeriesDaily?
https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=IBM&apikey=demo
Which has this JSON:
`{
"Meta Data": {
"1. Information": "Daily Prices (open, high, low, close) and Volumes",
"2. Symbol": "IBM",
"3. Last Refreshed": "2022-07-01",
"4. Output Size": "Compact",
"5. Time Zone": "US/Eastern"
},
"Time Series (Daily)": {
"2022-07-01": {
"1. open": "141.0000",
"2. high": "141.6700",
"3. low": "139.2600",
"4. close": "141.1200",
"5. volume": "4012106"
},
"2022-06-30": {
"1. open": "139.5800",
"2. high": "142.4600",
"3. low": "139.2800",
"4. close": "141.1900",
"5. volume": "4878020"
},
....
"2022-02-08": {
"1. open": "137.2300",
"2. high": "137.5200",
"3. low": "135.7800",
"4. close": "137.0200",
"5. volume": "4181825"
}
}
}
I'm wondering how to use the code... How do I specify the Dictionary of some number of Daily Dictionaries, for the JSON parsers?
struct Result: Codable {
let metaData: MetaData
let timeSeriesDaily: TimeSeriesDaily
private enum CodingKeys: String, CodingKey {
case metaData = "Meta Data"
case timeSeriesDaily = "Time Series (Daily)"
}
}
struct DailyTimeSeries: Codable { // use CoadableExtension for Dictionary [String: Any]
let daily: [String: DailyQuote]
}
I've banged my head on this a lot over the last few weeks. Finally a solution:
struct Daily: Codable {
let open: String
let high: String
let low: String
let close: String
let volume: String
private enum CodingKeys: String, CodingKey {
case open = "1. open"
case high = "2. high"
case low = "3. low"
case close = "4. close"
case volume = "5. volume"
}
}
struct MetaData: Codable {
let information: String
let symbol: String
let lastRefreshed: String
let outputSize: String
let timeZone: String
private enum CodingKeys: String, CodingKey {
case information = "1. Information"
case symbol = "2. Symbol"
case lastRefreshed = "3. Last Refreshed"
case outputSize = "4. Output Size"
case timeZone = "5. Time Zone"
}
}
struct Result: Codable {
let metaData: MetaData
let timeSeriesDaily: [String:Daily]
private enum CodingKeys: String, CodingKey {
case metaData = "Meta Data"
case timeSeriesDaily = "Time Series (Daily)"
}
}
@loudmouth thank you so much for sharing!
change decoder to check for bool at first. because
true
will be decoded to int 1, so check for bool will drop