Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save loudmouth/332e8d89d8de2c1eaf81875cfcd22e24 to your computer and use it in GitHub Desktop.

Select an option

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)
}
}
@haashem
Copy link
Copy Markdown

haashem commented Nov 21, 2017

change decoder to check for bool at first. because true will be decoded to int 1, so check for bool will drop

@loudmouth
Copy link
Copy Markdown
Author

@hashemp206 done, good catch ;-)

@converted2mac
Copy link
Copy Markdown

@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!

@sakrist
Copy link
Copy Markdown

sakrist commented Apr 2, 2018

@schlingding
Copy link
Copy Markdown

@OlesenkoViktor
Copy link
Copy Markdown

@loudmouth
Few mistakes:

  • if let if let boolValue -> if let boolValue
  • } else intValue -> } else if let intValue

Copy link
Copy Markdown

ghost commented Nov 22, 2018

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)
    }

@popochess
Copy link
Copy Markdown

dude! you save my time

@ChiellieNL
Copy link
Copy Markdown

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
    }

@mikebuss
Copy link
Copy Markdown

@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

@loudmouth
Copy link
Copy Markdown
Author

@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 ;-)

@sukov
Copy link
Copy Markdown

sukov commented Aug 26, 2019

@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

@davidforneron
Copy link
Copy Markdown

Thanks for sharing this! I really need it :)

@rhwood
Copy link
Copy Markdown

rhwood commented Jun 14, 2020

Thanks for this. I think if you renamed the gist to end in .swift instead of [space]Swift syntax highlighting will be added.

@noorulain17
Copy link
Copy Markdown

Thanks for sharing this 👍

@oteronavarretericardo
Copy link
Copy Markdown

oteronavarretericardo commented Apr 16, 2021

i tried @sukov version, but i still saw the same issues:
null were not appended to [Any].
One infinite loop and one stack overflow in UnkeyedDecodingContainer::decode()
Here's my updated version

@ronstar
Copy link
Copy Markdown

ronstar commented Aug 8, 2021

Is this supposed to be able to decode a struct with an [Any] parameter automatically? Or we need to manually decode it?

@davidakoontz
Copy link
Copy Markdown

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]
}

@davidakoontz
Copy link
Copy Markdown

davidakoontz commented Jul 6, 2022

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)"
    }
}

@pankova
Copy link
Copy Markdown

pankova commented Feb 27, 2024

@loudmouth thank you so much for sharing!

@mithyer
Copy link
Copy Markdown

mithyer commented Jan 9, 2025

I have implemented enum type solution: MixedObj.
Baseed on mbuchetics's solution, but much more flexble, with predefined type options.
https://github.com/mithyer/MixedObject

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment