Swift 4 decodable with unknown dynamic keys - json

I have the following JSON
{"DynamicKey":6410,"Meta":{"name":"","page":""}}
DynamicKey is unknown at compile time.I'm trying to find a reference how to
parse this struct using decodable.
public struct MyStruct: Decodable {
public let unknown: Double
public let meta: [String: String]
private enum CodingKeys: String, CodingKey {
case meta = "Meta"
}
}
Any ideas?

To decode an arbitrary string, you need a key like this:
// Arbitrary key
private struct Key: CodingKey, Hashable, CustomStringConvertible {
static let meta = Key(stringValue: "Meta")!
var description: String {
return stringValue
}
var hashValue: Int { return stringValue.hash }
static func ==(lhs: Key, rhs: Key) -> Bool {
return lhs.stringValue == rhs.stringValue
}
let stringValue: String
init(_ string: String) { self.stringValue = string }
init?(stringValue: String) { self.init(stringValue) }
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
This is a very general-purpose tool (expect for the static let meta) that can be used for all kinds of generic-key problems.
With that, you can find the first key that isn't .meta and use that as your dynamic key.
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Key.self)
meta = try container.decode([String: String].self, forKey: .meta)
guard let dynamicKey = container.allKeys.first(where: { $0 != .meta }) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [],
debugDescription: "Could not find dynamic key"))
}
unknown = try container.decode(Double.self, forKey: dynamicKey)
}
All together as a playground:
import Foundation
let json = Data("""
{"DynamicKey":6410,"Meta":{"name":"","page":""}}
""".utf8)
public struct MyStruct: Decodable {
public let unknown: Double
public let meta: [String: String]
// Arbitrary key
private struct Key: CodingKey, Hashable, CustomStringConvertible {
static let meta = Key(stringValue: "Meta")!
var description: String {
return stringValue
}
var hashValue: Int { return stringValue.hash }
static func ==(lhs: Key, rhs: Key) -> Bool {
return lhs.stringValue == rhs.stringValue
}
let stringValue: String
init(_ string: String) { self.stringValue = string }
init?(stringValue: String) { self.init(stringValue) }
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Key.self)
meta = try container.decode([String: String].self, forKey: .meta)
guard let dynamicKey = container.allKeys.first(where: { $0 != .meta }) else {
throw DecodingError.dataCorrupted(.init(codingPath: [],
debugDescription: "Could not find dynamic key"))
}
unknown = try container.decode(Double.self, forKey: dynamicKey)
}
}
let myStruct = try! JSONDecoder().decode(MyStruct.self, from: json)
myStruct.unknown
myStruct.meta
This technique can be expanded to decode arbitrary JSON. Sometimes it's easier to do that, and then pull out the pieces you want, then to decode each piece. For example, with the JSON gist above, you could implement MyStruct this way:
public struct MyStruct: Decodable {
public let unknown: Double
public let meta: [String: String]
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let json = try container.decode(JSON.self)
guard let meta = json["Meta"]?.dictionaryValue as? [String: String] else {
throw DecodingError.dataCorrupted(.init(codingPath: [],
debugDescription: "Could not find meta key"))
}
self.meta = meta
guard let (_, unknownJSON) = json.objectValue?.first(where: { (key, _) in key != "Meta" }),
let unknown = unknownJSON.doubleValue
else {
throw DecodingError.dataCorrupted(.init(codingPath: [],
debugDescription: "Could not find dynamic key"))
}
self.unknown = unknown
}
}

import UIKit
var str = """
{"DynamicKey":6410,"Meta":{"name":"","page":""}}
"""
public struct MyStruct: Decodable {
public var unknown: Double?
public var meta: [String: String]?
public init(from decoder: Decoder) {
guard let container = try? decoder.container(keyedBy: CodingKeys.self) else {
fatalError()
}
for key in container.allKeys {
unknown = try? container.decode(Double.self, forKey: key)//) ?? 0.0
if key.stringValue == "Meta" {
meta = try? container.decode([String: String].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 jsonData = str.data(using: .utf8)!
let jsonDecoder = JSONDecoder()
let myStruct = try! jsonDecoder.decode(MyStruct.self, from: jsonData)
print("Meta : \(myStruct.meta)")
print("Double : \(myStruct.unknown)")
I've already answered a similar question
https://stackoverflow.com/a/48412139/1979882

Related

How to resolve the typeMismatch error in SwiftUI?

This is my model. When I fetch the detail the an error occured.
// MARK: - PublisherModel
public struct PublisherModel: Decodable {
public let userslist: [UserslistModel]
}
// MARK: - UserslistModel
public struct UserslistModel: Decodable, Hashable {
public var id: String
public var full_name: String
public var totalBookViews: String
public var totalBooks: String?
public var full_address: String?
private enum CodingKeys: String, CodingKey {
case id,full_name,totalBookViews,totalBooks,full_address
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
full_name = try container.decode(String.self, forKey: .full_name)
full_address = try container.decode(String.self, forKey: .full_address)
totalBooks = try container.decode(String.self, forKey: .totalBooks)
totalBookViews = try container.decode(String.self, forKey: .totalBookViews)
do {
id = try String(container.decode(Int.self, forKey: .id))
} catch DecodingError.typeMismatch {
id = try container.decode(String.self, forKey: .id)
}
}
}
This is My ViewModel
class PublisherModelVM: ObservableObject {
#Published var datas = [UserslistModel]()
let url = "APIURL***?************/PublisherList" // For Security Reason I deleted The Api URl
init() {
getData(url: url)
}
func getData(url: String) {
guard let url = URL(string: url) else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
if let data = data {
do {
let results = try JSONDecoder().decode(PublisherModel.self, from: data).userslist
DispatchQueue.main.async {
self.datas = results.reversed()
}
}
catch {
print(error)
}
}
}.resume()
}
}
This is My View here I fetch the details
struct PublisherDataView: View{
#StateObject var list = PublisherModelVM()
#State var logourl: String?
var body: some View{
ScrollView(.horizontal,showsIndicators: false){
HStack{
ForEach( list.datas, id: \.id){ item in
VStack(spacing: 12){
Text(item.full_name )
Text(item.full_address ?? "-")
Text(item.totalBookViews)
Text(item.totalBooks!)
}
.frame(width:275, height: 220)
.background(Color.brown.opacity(0.5)).cornerRadius(12)
}
}
}
}
}
And when run this code the a error show that is:
Where am I wrong? Is it whenever I changed full_address with String?
Then also this issue appeared: none typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "userslist", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "totalBooks", intValue: nil)], debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil))
And please help me for resolve this problem with my Code.
First of all please delete all init methods in your structs. The compiler creates them on your behalf, and Codable doesn't use them anyway.
public init(userslist: [UserslistModel]) {
self.userslist = userslist
}
and
public init(id: Int, full_name: String, totalBookViews: String, totalBooks: Int, full_address: String) {
self.id = id
self.full_name = full_name
self.totalBookViews = totalBookViews
self.totalBooks = totalBooks
self.full_address = full_address
}
Second of all add the convertFromSnakeCase strategy to map the snake_case keys to camelCase struct members.
totalBookViews is sometimes Int and sometimes String. It's still more complicating: The string value cannot be directly converted to Int because it contains letters and whitespace characters.
You have to implement init(from decoder and decode totalBookViews conditionally. If it's a string, filter the numeric characters and convert the string to Int.
Another issue – already mentioned by Nirav D – is that fullAddress can be nil. The init method must consider that, too.
// MARK: - PublisherModel
public struct PublisherModel: Decodable {
public let userslist: [UserslistModel]
}
// MARK: - UserslistModel
public struct UserslistModel: Decodable {
public let id: Int
public let fullName: String
public let totalBookViews: Int
public let totalBooks: Int
public let fullAddress: String
private enum CodingKeys: String, CodingKey {
case id, fullName, totalBookViews, totalBooks, fullAddress
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.fullName = try container.decode(String.self, forKey: .fullName)
do {
self.totalBookViews = try container.decode(Int.self, forKey: .totalBookViews)
} catch {
let bookViews = try container.decode(String.self, forKey: .totalBookViews).filter(\.isNumber)
guard let bookViewsAmount = Int(bookViews) else {
throw DecodingError.dataCorruptedError(forKey: .totalBookViews, in: container, debugDescription: "Wrong String Format")
}
self.totalBookViews = bookViewsAmount
}
self.totalBooks = try container.decode(Int.self, forKey: .totalBooks)
self.fullAddress = try container.decodeIfPresent(String.self, forKey: .fullAddress) ?? ""
}
}
And create the decoder
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let results = try decoder.decode(PublisherModel.self, from: data).userslist

Decoding a Dictionary Inside a Dictionary - JSON/Swift

I have a JSON file that is structured like this:
"forecast": [
{
"altimeter": "",
"clouds": [
{
"repr": "OVC025",
"type": "OVC",
"altitude": 25,
"modifier": null,
"direction": null
}
],
"flight_rules": "MVFR",
"other": [],
"sanitized": "0318/0424 34017G26KT P6SM -RA OVC025",
"visibility": {
"repr": "P6",
"value": null,
"spoken": "greater than six"
},
"wind_direction": {
"repr": "340",
"value": 340,
"spoken": "three four zero"
}
And so on....
I am trying to access the information and update a UI using a DispatchQueue, but can't figure out how to pull data from inside forecast: clouds: repr (or any other nested like that). I can successfully pull data like: forecast: raw. I tried with the structs not nested in the first, but it didn't work (as expected, the data is another index inside).
My decoding file is:
//
// TAFData.swift
// AvWx Pro
//
// Created by Grayson Bertaina on 9/24/20.
//
import Foundation
struct TAFDatas: Codable {
let flight_rules: String?
let time: TimeTAF?
let station: String?
let raw: String?
let forecast: [ForecastTAF?]
let end_time: ?
let wind_gust: ?
}
struct ForecastTAF: Codable {
let raw: String?
struct CloudsTAF: Codable {
let type: String
let altitude: Int
}
struct endTimeTAF: Codable {
let repr: String
}
struct WindSpeedTAF: Codable {
let value: Int
}
struct WindGustTAF: Codable {
let value: Int
}
struct WindDirectionTAF: Codable {
let repr: String
}
struct VisibilityTAF: Codable {
let repr: String
}
struct WxcodesTAF: Codable {
let value: String
}
struct StartTimeTAF: Codable {
let repr: String
}
struct EndTimeTAF: Codable {
let repr: String
}
}
struct TimeTAF: Codable {
let repr: String
}
My Parsing file is:
//
// TAFManager.swift
// AvWx Pro
//
// Created by Grayson Bertaina on 9/24/20.
//
import Foundation
protocol TAFManagerDelegate : class {
func didUpdateTAF(_ weatherManager: TAFManager, weatherTAF: TAFModel)
func didFailWithErrorTAF(error: Error)
}
struct TAFManager {
let TAFURL = "https://avwx.rest/api/taf/"
weak var delegate : TAFManagerDelegate?
func fetchWeatherTAF (stationICAO: String) {
let TAFurlString = "\(TAFURL)\(stationICAO)?token=OVi45FiTDo1LmyodShfOfoizNe5m9wyuO6Mkc95AN-c"
performRequestTAF(with: TAFurlString)
}
func performRequestTAF (with TAFurlString: String) {
if let TAFurl = URL(string: TAFurlString) {
let session = URLSession(configuration: .default)
let taskTAF = session.dataTask(with: TAFurl) { (data, response, error) in
if error != nil {
self.delegate?.didFailWithErrorTAF(error: error!)
return
}
if let safeDataTAF = data {
if let weatherTAF = self.parseJSONTAF(safeDataTAF) {
self.delegate?.didUpdateTAF(self, weatherTAF: weatherTAF)
}
}
}
taskTAF.resume()
print(TAFurlString)
}
}
func parseJSONTAF(_ TAFData: Data) -> TAFModel? {
do {
let decoderTAF = JSONDecoder()
let decodedDataTAF = try decoderTAF.decode(TAFDatas.self, from: TAFData)
let cloudsTAF = decodedDataTAF.clouds
let wxcodesTAF = decodedDataTAF.wx_codes
let forecastTAF = decodedDataTAF.forecast
let lowCloudsTypeTAF = (cloudsTAF.count > 0 ? cloudsTAF[0]?.type : nil) ?? "N/A"
let midCloudsTypeTAF = (cloudsTAF.count > 1 ? cloudsTAF[1]?.type : nil) ?? "N/A"
let highCloudsTypeTAF = (cloudsTAF.count > 2 ? cloudsTAF[2]?.type : nil) ?? "N/A"
let lowCloudsAltTAF = (cloudsTAF.count > 0 ? cloudsTAF[0]?.altitude : nil) ?? 0
let midCloudsAltTAF = (cloudsTAF.count > 1 ? cloudsTAF[1]?.altitude : nil) ?? 0
let highCloudsAltTAF = (cloudsTAF.count > 2 ? cloudsTAF[2]?.altitude : nil) ?? 0
let reportingStationVarTAF = decodedDataTAF.station ?? "N/A"
let windGustValueTAF = decodedDataTAF.wind_gust?.value ?? 0
let windSpeedValueTAF = decodedDataTAF.wind_speed?.value ?? 0
let windDirectionValueTAF = decodedDataTAF.wind_direction?.repr ?? "N/A"
let visibilityValueTAF = decodedDataTAF.visibility?.repr ?? "N/A"
let flightRulesValueTAF = decodedDataTAF.flight_rules ?? "N/A"
let timeReportedTAF = decodedDataTAF.time?.repr ?? "N/A"
let firstWxCode1TAF = (wxcodesTAF.count > 0 ? wxcodesTAF[0]?.value : "N/A") ?? "N/A"
let startTimeTaf = decodedDataTAF.start_time?.repr ?? "N/A"
let endTimeTaf = (forecastTAF.count > 0 ? forecastTAF[0]? : nil) ?? "N/A"
let rawTAFData = (forecastTAF.count > 0 ? forecastTAF[0]?.raw : nil) ?? "N/A"
let weatherTAF = TAFModel(lowestCloudsTypeTAF: lowCloudsTypeTAF , lowestCloudsAltTAF: lowCloudsAltTAF, middleCloudsTypeTAF: midCloudsTypeTAF , middleCloudsAltTAF: midCloudsAltTAF, highestCloudsTypeTAF: highCloudsTypeTAF , highestCloudsAltTAF: highCloudsAltTAF, reportingStationTAF: reportingStationVarTAF, windGustTAF: windGustValueTAF, windSpeedTAF: windSpeedValueTAF, windDirectionTAF: windDirectionValueTAF, visibilityTAF: visibilityValueTAF, flightRulesTAF: flightRulesValueTAF, timeTAF: timeReportedTAF, startTimeTAF: startTimeTaf, endTimeTAF: endTimeTaf, firstWxCodeTAF: firstWxCode1TAF, rawTAF: rawTAFData)
delegate?.didUpdateTAF(self, weatherTAF: weatherTAF)
return weatherTAF
} catch {
delegate?.didFailWithErrorTAF(error: error)
return nil
}
}
}
My model file is:
//
// WeatherModel.swift
// AvWx Pro
//
// Created by Grayson Bertaina on 9/22/20.
//
import Foundation
struct WeatherModel {
let lowestCloudsType: String
let lowestCloudsAlt: Int
let middleCloudsType: String
let middleCloudsAlt: Int
let highestCloudsType: String
let highestCloudsAlt: Int
let reportingStation: String
let windGust: Int
let windSpeed: Int
let windDirection: String
let visibility: String
let flightRules: String
let time: String
let remarks: String
let altimeter: Double
let temperature: String
let dewpoint: String
let firstWxCode: String
var altToString1: String {
return String(format: "%u" + "00 ft", lowestCloudsAlt)
}
var altToString2: String {
return String(format: "%u" + "00 ft", middleCloudsAlt)
}
var altToString3: String {
return String(format: "%u" + "00 ft", highestCloudsAlt)
}
var windGustString: String {
return String(format: "%u" + "kt", windGust)
}
var windSpeedString: String {
return String(format: "%u" + "kt", windSpeed)
}
var altimeterString: String {
return String(format: "%.2f" + " inHg", altimeter as CVarArg)
}
var visUnits: String {
return visibility + " SM"
}
var degUnits: String {
return windDirection + "°"
}
var tempUnits: String {
return temperature + "°C"
}
var dewUnits: String {
return dewpoint + "°C"
}
var flightConditions: String {
switch flightRules {
case "VFR":
return "green"
case "MVFR":
return "blue"
case "IFR":
return "red"
case "LIFR":
return "purple"
default:
return "gray"
}
}
}
I think that the main hump is getting to those data keys. Once I'm there, hopefully the rest will fall into place. I really appreciate the help in advance, and have a great day!
With comment, my new JSON
// MARK: - TAFData
struct TAFData: Codable {
let meta: MetaTAF?
let raw, station: String?
let time: TimeTAF?
let remarks: String?
let forecast: [ForecastTAF?]
let startTime, endTime: TimeTAF?
let maxTemp, minTemp: String?
let alts, temps: JSONNull?
enum CodingKeys: String, CodingKey {
case meta, raw, station, time, remarks, forecast
case startTime = "start_time"
case endTime = "end_time"
case maxTemp = "max_temp"
case minTemp = "min_temp"
case alts, temps, units
}
}
// MARK: - Time
struct TimeTAF: Codable {
let repr, dt: String
}
// MARK: - Forecast
struct ForecastTAF: Codable {
let altimeter: String
let clouds: [CloudTAF]
let flightRules: String
let other: [JSONAny]
let sanitized: String
let visibility, windDirection: VisibilityTAF
let windGust: VisibilityTAF?
let windSpeed: VisibilityTAF
let wxCodes: [WxCodeTAF]
let endTime: TimeTAF
let icing: [JSONAny]
let probability: JSONNull?
let raw: String
let startTime: TimeTAF
let turbulence: [JSONAny]
let type: String
let windShear: JSONNull?
let summary: String
enum CodingKeys: String, CodingKey {
case altimeter, clouds
case flightRules = "flight_rules"
case other, sanitized, visibility
case windDirection = "wind_direction"
case windGust = "wind_gust"
case windSpeed = "wind_speed"
case wxCodes = "wx_codes"
case endTime = "end_time"
case icing, probability, raw
case startTime = "start_time"
case turbulence, type
case windShear = "wind_shear"
case summary
}
}
// MARK: - Cloud
struct CloudTAF: Codable {
let repr, type: String
let altitude: Int
let modifier, direction: JSONNull?
}
// MARK: - Visibility
struct VisibilityTAF: Codable {
let repr: String
let value: Int?
let spoken: String
}
// MARK: - WxCode
struct WxCodeTAF: Codable {
let repr, value: String
}
// MARK: - Meta
struct MetaTAF: Codable {
let timestamp: String
}
// MARK: - Units
struct UnitsTAF: Codable {
let altimeter, altitude, temperature, visibility: String
let windSpeed: String
enum CodingKeys: String, CodingKey {
case altimeter, altitude, temperature, visibility
case windSpeed = "wind_speed"
}
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public func hash(into hasher: inout Hasher) {
// No-op
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
class JSONCodingKey: CodingKey {
let key: String
required init?(intValue: Int) {
return nil
}
required init?(stringValue: String) {
key = stringValue
}
var intValue: Int? {
return nil
}
var stringValue: String {
return key
}
}
class JSONAny: Codable {
let value: Any
static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError {
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny")
return DecodingError.typeMismatch(JSONAny.self, context)
}
static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError {
let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny")
return EncodingError.invalidValue(value, context)
}
static func decode(from container: SingleValueDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if container.decodeNil() {
return JSONNull()
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if let value = try? container.decodeNil() {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer() {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decode(from container: inout KeyedDecodingContainer<JSONCodingKey>, forKey key: JSONCodingKey) throws -> Any {
if let value = try? container.decode(Bool.self, forKey: key) {
return value
}
if let value = try? container.decode(Int64.self, forKey: key) {
return value
}
if let value = try? container.decode(Double.self, forKey: key) {
return value
}
if let value = try? container.decode(String.self, forKey: key) {
return value
}
if let value = try? container.decodeNil(forKey: key) {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer(forKey: key) {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] {
var arr: [Any] = []
while !container.isAtEnd {
let value = try decode(from: &container)
arr.append(value)
}
return arr
}
static func decodeDictionary(from container: inout KeyedDecodingContainer<JSONCodingKey>) throws -> [String: Any] {
var dict = [String: Any]()
for key in container.allKeys {
let value = try decode(from: &container, forKey: key)
dict[key.stringValue] = value
}
return dict
}
static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws {
for value in array {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer()
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: JSONCodingKey.self)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}
static func encode(to container: inout KeyedEncodingContainer<JSONCodingKey>, dictionary: [String: Any]) throws {
for (key, value) in dictionary {
let key = JSONCodingKey(stringValue: key)!
if let value = value as? Bool {
try container.encode(value, forKey: key)
} else if let value = value as? Int64 {
try container.encode(value, forKey: key)
} else if let value = value as? Double {
try container.encode(value, forKey: key)
} else if let value = value as? String {
try container.encode(value, forKey: key)
} else if value is JSONNull {
try container.encodeNil(forKey: key)
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer(forKey: key)
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}
static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
public required init(from decoder: Decoder) throws {
if var arrayContainer = try? decoder.unkeyedContainer() {
self.value = try JSONAny.decodeArray(from: &arrayContainer)
} else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) {
self.value = try JSONAny.decodeDictionary(from: &container)
} else {
let container = try decoder.singleValueContainer()
self.value = try JSONAny.decode(from: container)
}
}
public func encode(to encoder: Encoder) throws {
if let arr = self.value as? [Any] {
var container = encoder.unkeyedContainer()
try JSONAny.encode(to: &container, array: arr)
} else if let dict = self.value as? [String: Any] {
var container = encoder.container(keyedBy: JSONCodingKey.self)
try JSONAny.encode(to: &container, dictionary: dict)
} else {
var container = encoder.singleValueContainer()
try JSONAny.encode(to: &container, value: self.value)
}
}
}
I would probably use a different model because you current one doesn't support the same format as the JSON:
struct Forecast: Codable {
let altimeter: String
let clouds: [Cloud]
let flightRules: String
let other: [JSONAny]
let sanitized: String
let visibility, windDirection: Visibility
enum CodingKeys: String, CodingKey {
case altimeter, clouds
case flightRules = "flight_rules"
case other, sanitized, visibility
case windDirection = "wind_direction"
}
}
// MARK: - Cloud
struct Cloud: Codable {
let repr, type: String
let altitude: Int
}
// MARK: - Visibility
struct Visibility: Codable {
let repr: String
let value: Int?
let spoken: String
}
This way you can just use:
if let cloud = forecast.clouds.first() {
let repr = cloud.repr
.. Your logic
}
There is probably things missing in my model, but you should get an idea

Decodable and JSON, 2 datatypes for same variable

I'm using the Decodable protocol to decode some json, but I've run into a problem:
I'm getting an answer back, where a longitude and a latitide can be either an interger (latitude = 0) if there's no geo location data added to the element, and a String (fx. latitude = "25.047880") if there's geodata available. Now when I decode the json, I don't know how to build my Struct, as the long and lat can't both be String and Int.. So I'm getting a decode error when fetching elements where both cases are represented.
Any suggestions about how to solve this? I've tried with "Any" as datatype, but this doesn't conform to the Decodable protocol
struct JPhoto: Decodable {
let id: String
let farm: Int
let secret: String
let server: String
let owner: String
let title: String
let latitude: String //Can both be Int and String
let longitude: String //Can both be Int and String
}
You need to write your own encoder/decoder. You can use an associated value enum to do this, using a switch statement to encode and the throwing/catching behaviour to decode:
enum AngularDistance:Codable {
case string(String), integer(Int)
func encode(to encoder: Encoder) throws {
switch self {
case .string(let str):
var container = encoder.singleValueContainer()
try container.encode(str)
case .integer(let int):
var container = encoder.singleValueContainer()
try container.encode(int)
}
}
init(from decoder: Decoder) throws {
do {
let container = try decoder.singleValueContainer()
let str = try container.decode(String.self)
self = AngularDistance.string(str)
}
catch {
do { let container = try decoder.singleValueContainer()
let int = try container.decode(Int.self)
self = AngularDistance.integer(int)
}
catch {
throw DecodingError.typeMismatch(AngularDistance.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Expected to decode an Int or a String"))
}
}
}
}
Here's an example of encoding and decoding this AngularDistance type:
let lat = [AngularDistance.string("String"), AngularDistance.integer(10)]
let encoder = JSONEncoder()
var decoder = JSONDecoder()
do {
let encoded = try encoder.encode(lat)
try decoder.decode(Array<AngularDistance>.self, from: encoded)
}
catch DecodingError.typeMismatch(let t, let e) {
t
e.codingPath
e.debugDescription
}
catch {
print(error.localizedDescription)
}
And here's your struct rewritten:
struct JPhoto: Decodable {
let id: String
let farm: Int
let secret: String
let server: String
let owner: String
let title: String
let latitude: AngularDistance //Can both be Int and String
let longitude: AngularDistance //Can both be Int and String
}
There are couple of approaches to mention in addition to enums with associated value. You can use proposed Either<Int, String> or IntOrString struct for your latitude and longitude.
struct Either<F: Codable, S: Codable>: Codable {
var firstValue: F?
var secondValue: S?
var value: Any? {
return firstValue ?? secondValue
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
if firstValue != nil {
try? container.encode(firstValue)
} else {
try? container.encode(secondValue)
}
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
firstValue = try? container.decode(F.self)
secondValue = try? container.decode(S.self)
if firstValue == nil && secondValue == nil {
//Type mismatch
throw DecodingError.typeMismatch(type(of: self), DecodingError.Context(codingPath: [], debugDescription: "The value is not of type \(F.self) and also not \(S.self)"))
}
}
}
Another way to do the same:
struct IntOrString: Codable {
var value: Any
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
if let intValue = value as? Int {
try? container.encode(intValue)
} else if let strValue = value as? String {
try? container.encode(strValue)
}
}
init(from decoder: Decoder) throws {
if let int = try? Int(from: decoder) {
value = int
return
}
value = try String(from: decoder)
}
}

Any when decoding JSON with Codable?

With Swift 3 JSONSerialization, if a part of your data model was completely dynamic, you could always just leave the deserialized data at Any and let the consumer deal with it.
With Swift 4's Codable, I'd like to do this:
struct Foo : Codable {
let bar: Any;
}
But I get
JSONPlayground.playground:4:9: note: cannot automatically synthesize 'Decodable' because 'Any' does not conform to 'Decodable'
let bar: Any;
^
That alone wouldn't be the end of the world if I could implement my own Decodable, but that would require the Decoder to support decoding to Any, which as far as I can tell it does not. For example:
extension Foo {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let result = try container.decode(AnyClass.self)
}
}
gives
error: JSONPlayground.playground:4:36: error: cannot invoke 'decode' with an argument list of type '(AnyClass.Protocol)'
let result = try container.decode(AnyClass.self)
^
Is there any solution to this?
I ended up having to implement my own class to encode/decode Any values. It's not pretty, but it seems to work:
class JSONAny: Codable {
public let value: Any
static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError {
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny")
return DecodingError.typeMismatch(JSONAny.self, context)
}
static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError {
let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny")
return EncodingError.invalidValue(value, context)
}
static func decode(from container: SingleValueDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if container.decodeNil() {
return JSONNull()
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if let value = try? container.decodeNil() {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer() {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: MyCodingKey.self) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decode(from container: inout KeyedDecodingContainer, forKey key: MyCodingKey) throws -> Any {
if let value = try? container.decode(Bool.self, forKey: key) {
return value
}
if let value = try? container.decode(Int64.self, forKey: key) {
return value
}
if let value = try? container.decode(Double.self, forKey: key) {
return value
}
if let value = try? container.decode(String.self, forKey: key) {
return value
}
if let value = try? container.decodeNil(forKey: key) {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer(forKey: key) {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: MyCodingKey.self, forKey: key) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] {
var arr: [Any] = []
while !container.isAtEnd {
let value = try decode(from: &container)
arr.append(value)
}
return arr
}
static func decodeDictionary(from container: inout KeyedDecodingContainer) throws -> [String: Any] {
var dict = [String: Any]()
for key in container.allKeys {
let value = try decode(from: &container, forKey: key)
dict[key.stringValue] = value
}
return dict
}
static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws {
for value in array {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer()
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: MyCodingKey.self)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}
static func encode(to container: inout KeyedEncodingContainer, dictionary: [String: Any]) throws {
for (key, value) in dictionary {
let key = MyCodingKey(stringValue: key)!
if let value = value as? Bool {
try container.encode(value, forKey: key)
} else if let value = value as? Int64 {
try container.encode(value, forKey: key)
} else if let value = value as? Double {
try container.encode(value, forKey: key)
} else if let value = value as? String {
try container.encode(value, forKey: key)
} else if value is JSONNull {
try container.encodeNil(forKey: key)
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer(forKey: key)
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: MyCodingKey.self, forKey: key)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}
static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
public required init(from decoder: Decoder) throws {
if var arrayContainer = try? decoder.unkeyedContainer() {
self.value = try JSONAny.decodeArray(from: &arrayContainer)
} else if var container = try? decoder.container(keyedBy: MyCodingKey.self) {
self.value = try JSONAny.decodeDictionary(from: &container)
} else {
let container = try decoder.singleValueContainer()
self.value = try JSONAny.decode(from: container)
}
}
public func encode(to encoder: Encoder) throws {
if let arr = self.value as? [Any] {
var container = encoder.unkeyedContainer()
try JSONAny.encode(to: &container, array: arr)
} else if let dict = self.value as? [String: Any] {
var container = encoder.container(keyedBy: MyCodingKey.self)
try JSONAny.encode(to: &container, dictionary: dict)
} else {
var container = encoder.singleValueContainer()
try JSONAny.encode(to: &container, value: self.value)
}
}
}
class JSONNull: Codable {
public init() {
}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
class MyCodingKey : CodingKey {
let key: String
required init?(intValue: Int) {
return nil
}
required init?(stringValue: String) {
key = stringValue
}
var intValue: Int? {
return nil
}
var stringValue: String {
return key
}
}
Simply you can use AnyCodable type from Matt Thompson's cool library AnyCodable.
Eg:
import AnyCodable
struct Foo: Codable
{
var bar: AnyCodable
}
I solved my problem creating an AnyValue struct to allow Encode and Decode Any values from JSON:
To use it is pretty simple:
class MyClass: Codable {
var data: [String: AnyValue?]?
init(data: [String: AnyValue?]) {
self.data = data
}
}
let data = ["a": AnyValue(3), "b": AnyValue(true), "c": AnyValue("Rodrigo"), "d": AnyValue(3.3)]
let myClass = MyClass(data: data)
if let json = JsonUtil<MyClass>.toJson(myClass) {
print(json) // {"data":{"d":3.3,"b":true,"c":"Rodrigo","a":3}}
if let data = JsonUtil<MyClass>.from(json: json)?.data {
print(data["a"]??.value() ?? "nil") // 3
print(data["b"]??.value() ?? "nil") // true
print(data["c"]??.value() ?? "nil") // Rodrigo
print(data["d"]??.value() ?? "nil") // 3.3
}
}
AnyValue struct:
struct AnyValue: Codable {
private var int: Int?
private var string: String?
private var bool: Bool?
private var double: Double?
init(_ int: Int) {
self.int = int
}
init(_ string: String) {
self.string = string
}
init(_ bool: Bool) {
self.bool = bool
}
init(_ double: Double) {
self.double = double
}
init(from decoder: Decoder) throws {
if let int = try? decoder.singleValueContainer().decode(Int.self) {
self.int = int
return
}
if let string = try? decoder.singleValueContainer().decode(String.self) {
self.string = string
return
}
if let bool = try? decoder.singleValueContainer().decode(Bool.self) {
self.bool = bool
return
}
if let double = try? decoder.singleValueContainer().decode(Double.self) {
self.double = double
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
if let anyValue = self.value() {
if let value = anyValue as? Int {
try container.encode(value)
return
}
if let value = anyValue as? String {
try container.encode(value)
return
}
if let value = anyValue as? Bool {
try container.encode(value)
return
}
if let value = anyValue as? Double {
try container.encode(value)
return
}
}
try container.encodeNil()
}
func value() -> Any? {
return self.int ?? self.string ?? self.bool ?? self.double
}
}
And I created a JsonUtil struct too:
struct JsonUtil<T: Codable> {
public static func from(json: String) -> T? {
if let jsonData = json.data(using: .utf8) {
let jsonDecoder = JSONDecoder()
do {
return try jsonDecoder.decode(T.self, from: jsonData)
} catch {
print(error)
}
}
return nil
}
public static func toJson(_ obj: T) -> String? {
let jsonEncoder = JSONEncoder()
do {
let jsonData = try jsonEncoder.encode(obj)
return String(data: jsonData, encoding: String.Encoding.utf8)
} catch {
print(error)
return nil
}
}
}
If you need a new type like [String] per example, you just need add it on AnyValue struct.
Good luck :)
You could try BeyovaJSON
import BeyovaJSON
struct Foo: Codable {
let bar: JToken
}
let foo = try! JSONDecoder().decode(Foo.self, from: data)

How to decode a property with type of JSON dictionary in Swift [45] decodable protocol

Let's say I have Customer data type which contains a metadata property that can contains any JSON dictionary in the customer object
struct Customer {
let id: String
let email: String
let metadata: [String: Any]
}
{
"object": "customer",
"id": "4yq6txdpfadhbaqnwp3",
"email": "john.doe#example.com",
"metadata": {
"link_id": "linked-id",
"buy_count": 4
}
}
The metadata property can be any arbitrary JSON map object.
Before I can cast the property from a deserialized JSON from NSJSONDeserialization but with the new Swift 4 Decodable protocol, I still can't think of a way to do that.
Do anyone know how to achieve this in Swift 4 with Decodable protocol?
With some inspiration from this gist I found, I wrote some extensions for UnkeyedDecodingContainer and KeyedDecodingContainer. You can find a link to my gist here. By using this code you can now decode any Array<Any> or Dictionary<String, Any> with the familiar syntax:
let dictionary: [String: Any] = try container.decode([String: Any].self, forKey: key)
or
let array: [Any] = try container.decode([Any].self, forKey: key)
Edit: there is one caveat I have found which is decoding an array of dictionaries [[String: Any]] The required syntax is as follows. You'll likely want to throw an error instead of force casting:
let items: [[String: Any]] = try container.decode(Array<Any>.self, forKey: .items) as! [[String: Any]]
EDIT 2: If you simply want to convert an entire file to a dictionary, you are better off sticking with api from JSONSerialization as I have not figured out a way to extend JSONDecoder itself to directly decode a dictionary.
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
// appropriate error handling
return
}
The extensions
// 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
}
guard try decodeNil(forKey: key) == false 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
}
guard try decodeNil(forKey: key) == false 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 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 if let 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 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 {
// See if the current value in the JSON array is `null` first and prevent infite recursion with nested arrays.
if try decodeNil() {
continue
} else 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)
}
}
I have played with this problem, too, and finally wrote a simple library for working with “generic JSON” types. (Where “generic” means “with no structure known in advance”.) Main point is representing the generic JSON with a concrete type:
public enum JSON {
case string(String)
case number(Float)
case object([String:JSON])
case array([JSON])
case bool(Bool)
case null
}
This type can then implement Codable and Equatable.
You can create metadata struct which conforms to Decodable protocol and use JSONDecoder class to create object from data by using decode method like below
let json: [String: Any] = [
"object": "customer",
"id": "4yq6txdpfadhbaqnwp3",
"email": "john.doe#example.com",
"metadata": [
"link_id": "linked-id",
"buy_count": 4
]
]
struct Customer: Decodable {
let object: String
let id: String
let email: String
let metadata: Metadata
}
struct Metadata: Decodable {
let link_id: String
let buy_count: Int
}
let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
let decoder = JSONDecoder()
do {
let customer = try decoder.decode(Customer.self, from: data)
print(customer)
} catch {
print(error.localizedDescription)
}
I came with a slightly different solution.
Let's suppose we have something more than a simple [String: Any] to parse were Any might be an array or a nested dictionary or a dictionary of arrays.
Something like this:
var json = """
{
"id": 12345,
"name": "Giuseppe",
"last_name": "Lanza",
"age": 31,
"happy": true,
"rate": 1.5,
"classes": ["maths", "phisics"],
"dogs": [
{
"name": "Gala",
"age": 1
}, {
"name": "Aria",
"age": 3
}
]
}
"""
Well, this is my solution:
public struct AnyDecodable: Decodable {
public var value: Any
private struct CodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
init?(stringValue: String) { self.stringValue = stringValue }
}
public init(from decoder: Decoder) throws {
if let container = try? decoder.container(keyedBy: CodingKeys.self) {
var result = [String: Any]()
try container.allKeys.forEach { (key) throws in
result[key.stringValue] = try container.decode(AnyDecodable.self, forKey: key).value
}
value = result
} else if var container = try? decoder.unkeyedContainer() {
var result = [Any]()
while !container.isAtEnd {
result.append(try container.decode(AnyDecodable.self).value)
}
value = result
} else if let container = try? decoder.singleValueContainer() {
if let intVal = try? container.decode(Int.self) {
value = intVal
} else if let doubleVal = try? container.decode(Double.self) {
value = doubleVal
} else if let boolVal = try? container.decode(Bool.self) {
value = boolVal
} else if let stringVal = try? container.decode(String.self) {
value = stringVal
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "the container contains nothing serialisable")
}
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not serialise"))
}
}
}
Try it using
let stud = try! JSONDecoder().decode(AnyDecodable.self, from: jsonData).value as! [String: Any]
print(stud)
When I found the old answer, I only tested a simple JSON object case but not an empty one which will cause a runtime exception like #slurmomatic and #zoul found. Sorry for this issue.
So I try another way by having a simple JSONValue protocol, implement the AnyJSONValue type erasure struct and use that type instead of Any. Here's an implementation.
public protocol JSONType: Decodable {
var jsonValue: Any { get }
}
extension Int: JSONType {
public var jsonValue: Any { return self }
}
extension String: JSONType {
public var jsonValue: Any { return self }
}
extension Double: JSONType {
public var jsonValue: Any { return self }
}
extension Bool: JSONType {
public var jsonValue: Any { return self }
}
public struct AnyJSONType: JSONType {
public let jsonValue: Any
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let intValue = try? container.decode(Int.self) {
jsonValue = intValue
} else if let stringValue = try? container.decode(String.self) {
jsonValue = stringValue
} else if let boolValue = try? container.decode(Bool.self) {
jsonValue = boolValue
} else if let doubleValue = try? container.decode(Double.self) {
jsonValue = doubleValue
} else if let doubleValue = try? container.decode(Array<AnyJSONType>.self) {
jsonValue = doubleValue
} else if let doubleValue = try? container.decode(Dictionary<String, AnyJSONType>.self) {
jsonValue = doubleValue
} else {
throw DecodingError.typeMismatch(JSONType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unsupported JSON tyep"))
}
}
}
And here is how to use it when decoding
metadata = try container.decode ([String: AnyJSONValue].self, forKey: .metadata)
The problem with this issue is that we must call value.jsonValue as? Int. We need to wait until Conditional Conformance land in Swift, that would solve this problem or at least help it to be better.
[Old Answer]
I post this question on the Apple Developer forum and it turns out it is very easy.
I can do
metadata = try container.decode ([String: Any].self, forKey: .metadata)
in the initializer.
It was my bad to miss that in the first place.
If you use SwiftyJSON to parse JSON, you can update to 4.1.0 which has Codable protocol support. Just declare metadata: JSON and you're all set.
import SwiftyJSON
struct Customer {
let id: String
let email: String
let metadata: JSON
}
I have written an article and repo that helps in adding [String: Any] support for Codable for decoding as well as encoding.
https://medium.com/nerd-for-tech/string-any-support-for-codable-4ba062ce62f2
This improves on decodable aspect and also add encodable support as solution given by in https://stackoverflow.com/a/46049763/9160905
what you will be able to achieve:
json:
sample code:
You might have a look at BeyovaJSON
import BeyovaJSON
struct Customer: Codable {
let id: String
let email: String
let metadata: JToken
}
//create a customer instance
customer.metadata = ["link_id": "linked-id","buy_count": 4]
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
print(String(bytes: try! encoder.encode(customer), encoding: .utf8)!)
Here is more generic (not only [String: Any], but [Any] can decoded) and encapsulated approach (separate entity is used for that) inspired by #loudmouth answer.
Using it will look like:
extension Customer: Decodable {
public init(from decoder: Decoder) throws {
let selfContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try selfContainer.decode(.id)
email = try selfContainer.decode(.email)
let metadataContainer: JsonContainer = try selfContainer.decode(.metadata)
guard let metadata = metadataContainer.value as? [String: Any] else {
let context = DecodingError.Context(codingPath: [CodingKeys.metadata], debugDescription: "Expected '[String: Any]' for 'metadata' key")
throw DecodingError.typeMismatch([String: Any].self, context)
}
self.metadata = metadata
}
private enum CodingKeys: String, CodingKey {
case id, email, metadata
}
}
JsonContainer is a helper entity we use to wrap decoding JSON data to JSON object (either array or dictionary) without extending *DecodingContainer (so it won't interfere with rare cases when a JSON object is not meant by [String: Any]).
struct JsonContainer {
let value: Any
}
extension JsonContainer: Decodable {
public init(from decoder: Decoder) throws {
if let keyedContainer = try? decoder.container(keyedBy: Key.self) {
var dictionary = [String: Any]()
for key in keyedContainer.allKeys {
if let value = try? keyedContainer.decode(Bool.self, forKey: key) {
// Wrapping numeric and boolean types in `NSNumber` is important, so `as? Int64` or `as? Float` casts will work
dictionary[key.stringValue] = NSNumber(value: value)
} else if let value = try? keyedContainer.decode(Int64.self, forKey: key) {
dictionary[key.stringValue] = NSNumber(value: value)
} else if let value = try? keyedContainer.decode(Double.self, forKey: key) {
dictionary[key.stringValue] = NSNumber(value: value)
} else if let value = try? keyedContainer.decode(String.self, forKey: key) {
dictionary[key.stringValue] = value
} else if (try? keyedContainer.decodeNil(forKey: key)) ?? false {
// NOP
} else if let value = try? keyedContainer.decode(JsonContainer.self, forKey: key) {
dictionary[key.stringValue] = value.value
} else {
throw DecodingError.dataCorruptedError(forKey: key, in: keyedContainer, debugDescription: "Unexpected value for \(key.stringValue) key")
}
}
value = dictionary
} else if var unkeyedContainer = try? decoder.unkeyedContainer() {
var array = [Any]()
while !unkeyedContainer.isAtEnd {
let container = try unkeyedContainer.decode(JsonContainer.self)
array.append(container.value)
}
value = array
} else if let singleValueContainer = try? decoder.singleValueContainer() {
if let value = try? singleValueContainer.decode(Bool.self) {
self.value = NSNumber(value: value)
} else if let value = try? singleValueContainer.decode(Int64.self) {
self.value = NSNumber(value: value)
} else if let value = try? singleValueContainer.decode(Double.self) {
self.value = NSNumber(value: value)
} else if let value = try? singleValueContainer.decode(String.self) {
self.value = value
} else if singleValueContainer.decodeNil() {
value = NSNull()
} else {
throw DecodingError.dataCorruptedError(in: singleValueContainer, debugDescription: "Unexpected value")
}
} else {
let context = DecodingError.Context(codingPath: [], debugDescription: "Invalid data format for JSON")
throw DecodingError.dataCorrupted(context)
}
}
private struct Key: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
self.init(stringValue: "\(intValue)")
self.intValue = intValue
}
}
}
Note that numberic and boolean types are backed by NSNumber, else something like this won't work:
if customer.metadata["keyForInt"] as? Int64 { // as it always will be nil
I have made a pod to facilitate the way the decoding + encoding [String: Any], [Any]. And this provides encode or decode the optional properties, here https://github.com/levantAJ/AnyCodable
pod 'DynamicCodable', '1.0'
How to use it:
import DynamicCodable
struct YourObject: Codable {
var dict: [String: Any]
var array: [Any]
var optionalDict: [String: Any]?
var optionalArray: [Any]?
enum CodingKeys: String, CodingKey {
case dict
case array
case optionalDict
case optionalArray
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
dict = try values.decode([String: Any].self, forKey: .dict)
array = try values.decode([Any].self, forKey: .array)
optionalDict = try values.decodeIfPresent([String: Any].self, forKey: .optionalDict)
optionalArray = try values.decodeIfPresent([Any].self, forKey: .optionalArray)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(dict, forKey: .dict)
try container.encode(array, forKey: .array)
try container.encodeIfPresent(optionalDict, forKey: .optionalDict)
try container.encodeIfPresent(optionalArray, forKey: .optionalArray)
}
}
Details
Xcode 12.0.1 (12A7300)
Swift 5.3
Based on Tai Le library
// code from: https://github.com/levantAJ/AnyCodable/blob/master/AnyCodable/DecodingContainer%2BAnyCollection.swift
private
struct AnyCodingKey: CodingKey {
let stringValue: String
private (set) var intValue: Int?
init?(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) {
self.intValue = intValue
stringValue = String(intValue)
}
}
extension KeyedDecodingContainer {
private
func decode(_ type: [Any].Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> [Any] {
var values = try nestedUnkeyedContainer(forKey: key)
return try values.decode(type)
}
private
func decode(_ type: [String: Any].Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> [String: Any] {
try nestedContainer(keyedBy: AnyCodingKey.self, forKey: key).decode(type)
}
func decode(_ type: [String: Any].Type) throws -> [String: Any] {
var dictionary: [String: Any] = [:]
for key in allKeys {
if try decodeNil(forKey: key) {
dictionary[key.stringValue] = NSNull()
} else if let bool = try? decode(Bool.self, forKey: key) {
dictionary[key.stringValue] = bool
} else if let string = try? decode(String.self, forKey: key) {
dictionary[key.stringValue] = string
} else if let int = try? decode(Int.self, forKey: key) {
dictionary[key.stringValue] = int
} else if let double = try? decode(Double.self, forKey: key) {
dictionary[key.stringValue] = double
} else if let dict = try? decode([String: Any].self, forKey: key) {
dictionary[key.stringValue] = dict
} else if let array = try? decode([Any].self, forKey: key) {
dictionary[key.stringValue] = array
}
}
return dictionary
}
}
extension UnkeyedDecodingContainer {
mutating func decode(_ type: [Any].Type) throws -> [Any] {
var elements: [Any] = []
while !isAtEnd {
if try decodeNil() {
elements.append(NSNull())
} else if let int = try? decode(Int.self) {
elements.append(int)
} else if let bool = try? decode(Bool.self) {
elements.append(bool)
} else if let double = try? decode(Double.self) {
elements.append(double)
} else if let string = try? decode(String.self) {
elements.append(string)
} else if let values = try? nestedContainer(keyedBy: AnyCodingKey.self),
let element = try? values.decode([String: Any].self) {
elements.append(element)
} else if var values = try? nestedUnkeyedContainer(),
let element = try? values.decode([Any].self) {
elements.append(element)
}
}
return elements
}
}
Solution
struct DecodableDictionary: Decodable {
typealias Value = [String: Any]
let dictionary: Value?
init(from decoder: Decoder) throws {
dictionary = try? decoder.container(keyedBy: AnyCodingKey.self).decode(Value.self)
}
}
Usage
struct Model: Decodable {
let num: Double?
let flag: Bool?
let dict: DecodableDictionary?
let dict2: DecodableDictionary?
let dict3: DecodableDictionary?
}
let data = try! JSONSerialization.data(withJSONObject: dictionary)
let object = try JSONDecoder().decode(Model.self, from: data)
print(object.dict?.dictionary) // prints [String: Any]
print(object.dict2?.dictionary) // prints nil
print(object.dict3?.dictionary) // prints nil
I used some of the answers on this topic to get the simplest solution possible for me. My problem is that I was receiving a [String: Any] type dictionary, but I could very well work with a [String: String] transforming every other Any value in String. So this is my solution:
struct MetadataType: Codable {
let value: String?
private init(_ value: String?) {
self.value = value
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let decodedValue = try? container.decode(Int.self) {
self.init(String(decodedValue))
} else if let decodedValue = try? container.decode(Double.self) {
self.init(String(decodedValue))
} else if let decodedValue = try? container.decode(Bool.self) {
self.init(String(decodedValue))
} else if let decodedValue = try? container.decode(String.self) {
self.init(decodedValue)
} else {
self.init(nil)
}
}
}
And when declaring my dictionary, I use
let userInfo: [String: MetadataType]
The easiest and suggested way is to create separate model for each dictionary or model that is in JSON.
Here is what I do
//Model for dictionary **Metadata**
struct Metadata: Codable {
var link_id: String?
var buy_count: Int?
}
//Model for dictionary **Customer**
struct Customer: Codable {
var object: String?
var id: String?
var email: String?
var metadata: Metadata?
}
//Here is our decodable parser that decodes JSON into expected model
struct CustomerParser {
var customer: Customer?
}
extension CustomerParser: Decodable {
//keys that matches exactly with JSON
enum CustomerKeys: String, CodingKey {
case object = "object"
case id = "id"
case email = "email"
case metadata = "metadata"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CustomerKeys.self) // defining our (keyed) container
let object: String = try container.decode(String.self, forKey: .object) // extracting the data
let id: String = try container.decode(String.self, forKey: .id) // extracting the data
let email: String = try container.decode(String.self, forKey: .email) // extracting the data
//Here I have used metadata model instead of dictionary [String: Any]
let metadata: Metadata = try container.decode(Metadata.self, forKey: .metadata) // extracting the data
self.init(customer: Customer(object: object, id: id, email: email, metadata: metadata))
}
}
Usage:
if let url = Bundle.main.url(forResource: "customer-json-file", withExtension: "json") {
do {
let jsonData: Data = try Data(contentsOf: url)
let parser: CustomerParser = try JSONDecoder().decode(CustomerParser.self, from: jsonData)
print(parser.customer ?? "null")
} catch {
}
}
**I have used optional to be in safe side while parsing, can be changed as needed.
Read more on this topic
decode using decoder and coding keys
public let dataToDecode: [String: AnyDecodable]
enum CodingKeys: CodingKey {
case dataToDecode
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.dataToDecode = try container.decode(Dictionary<String, AnyDecodable>.self, forKey: .dataToDecode)
}
This will work
public struct AnyDecodable: Decodable {
public let value: Any
public init<T>(_ value: T?) {
self.value = value ?? ()
}
}
let contentDecodable = try values.decodeIfPresent(AnyDecodable.self, forKey: .content)
extension ViewController {
func swiftyJson(){
let url = URL(string: "https://itunes.apple.com/search?term=jack+johnson")
//let url = URL(string: "http://makani.bitstaging.in/api/business/businesses_list")
Alamofire.request(url!, method: .get, parameters: nil).responseJSON { response in
var arrayIndexes = [IndexPath]()
switch(response.result) {
case .success(_):
let data = response.result.value as! [String : Any]
if let responseData = Mapper<DataModel>().map(JSON: data) {
if responseData.results!.count > 0{
self.arrayExploreStylistList = []
}
for i in 0..<responseData.results!.count{
arrayIndexes.append(IndexPath(row: self.arrayExploreStylistList.count + i, section: 0))
}
self.arrayExploreStylistList.append(contentsOf: responseData.results!)
print(arrayIndexes.count)
}
// if let arrNew = data["results"] as? [[String : Any]]{
// let jobData = Mapper<DataModel>().mapArray(JSONArray: arrNew)
// print(jobData)
// self.datamodel = jobData
// }
self.tblView.reloadData()
break
case .failure(_):
print(response.result.error as Any)
break
}
}
}
}