I have a requirement to encode/decode snakeCased JSONs. I found that encoder encodes Value2 object correctly, however decoder fails to decode it. What I do wrong here?
Required Json format:
{
"address_line_1" : "Address",
"full_name" : "Name",
"id" : 2
}
Code:
struct Value1: Codable {
let id: Int
let fullName: String
let addressLine1: String
}
struct Value2: Codable {
let id: Int
let fullName: String
let addressLine_1: String
}
func printJson(_ object: Data) throws {
let json = try JSONSerialization.jsonObject(with: object, options: [])
let data = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted, .sortedKeys])
print(String(data: data, encoding: .utf8)!)
}
func encode<T: Encodable>(_ object: T) throws -> Data {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
return try encoder.encode(object)
}
func decode<T: Decodable>(_ type: T.Type, from data: Data) throws {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
_ = try decoder.decode(type, from: data)
print("✅ Decoded \(type) from:")
try printJson(data)
}
do {
var data: Data
data = try encode(Value1(id: 1, fullName: "Name", addressLine1: "Address"))
try decode(Value1.self, from: data)
data = try encode(Value2(id: 2, fullName: "Name", addressLine_1: "Address"))
_ = try decode(Value1.self, from: data)
_ = try decode(Value2.self, from: data)
} catch {
print("❌ Failed with error:", error)
}
Output:
✅ Decoded Value1 from:
{
"address_line1" : "Address",
"full_name" : "Name",
"id" : 1
}
✅ Decoded Value1 from:
{
"address_line_1" : "Address",
"full_name" : "Name",
"id" : 2
}
❌ Failed with error: keyNotFound(CodingKeys(stringValue: "addressLine_1", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"addressLine_1\", intValue: nil) (\"addressLine_1\"), with divergent representation addressLine1, converted to address_line_1.", underlyingError: nil))
convertFromSnakeCase works correctly and you can can check it in first decode:
_ = try decode(Value1.self, from: data)
After that, when you try to decode the same data but with Value2 type it surely fails as it expects different property name. This is your encoded snake case JSON:
{
"address_line_1" : "Address",
"full_name" : "Name",
"id" : 2
}
After decoder conversion address_line_1 becomes addressLine1 (the same applies to full_name) which fits properties of Value1. If you try to decode the same data for Value2 it fails as property name requires addressLine_1.
In your case, optimal strategy would be to use custom coding keys, like this:
struct Value2: Codable {
private enum Value2CodingKey: String, CodingKey {
case id
case fullName = "full_name"
case addressLine1 = "address_line_1"
}
let id: Int
let fullName: String
let addressLine1: String
}
I found a solution without using custom coding keys, but custom coding strategy instead, so coders handle _ before numbers as well.
So that addressLine1 encodes to address_line_1, and address_line_1 decodes to addressLine1
Usage:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCaseWithNumbers
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCaseWithNumbers
Coder implementation:
extension JSONEncoder.KeyEncodingStrategy {
static var convertToSnakeCaseWithNumbers: JSONEncoder.KeyEncodingStrategy {
.custom { codingKeys -> CodingKey in
let stringValue = codingKeys.last!.stringValue
let newKey = AnyKey(stringValue: convertToSnakeCase(stringValue))!
return newKey
}
}
private static func convertToSnakeCase(_ stringKey: String) -> String {
var key = stringKey
let searchRange = key.index(after: key.startIndex)..<key.endIndex
let nsRange = key.nsRange(from: searchRange)
let matches = NSRegularExpression("([A-Z])|([0-9]+)").matches(in: key, options: [], range: nsRange)
for match in matches.reversed() {
guard let range = key.range(from: match.range) else { continue }
key.insert("_", at: range.lowerBound)
}
return key.lowercased()
}
}
extension JSONDecoder.KeyDecodingStrategy {
static var convertFromSnakeCaseWithNumbers: JSONDecoder.KeyDecodingStrategy {
.custom { (codingKeys) -> CodingKey in
let stringValue = codingKeys.last!.stringValue
let newKey = AnyKey(stringValue: convertFromSnakeCase(stringValue))!
return newKey
}
}
private static func convertFromSnakeCase(_ stringKey: String) -> String {
guard stringKey.contains("_") else {
return stringKey
}
let components = stringKey.split(separator: "_").map({ $0.firstCapitalized })
return components.joined().firstLowercased
}
}
private extension NSRegularExpression {
convenience init(_ pattern: String) {
do {
try self.init(pattern: pattern)
} catch {
preconditionFailure("Illegal regular expression: \(pattern).")
}
}
}
private extension StringProtocol {
var firstLowercased: String { prefix(1).lowercased() + dropFirst() }
var firstCapitalized: String { prefix(1).capitalized + dropFirst() }
}
enum AnyKey: CodingKey {
case string(String)
case int(Int)
var stringValue: String {
switch self {
case .string(let string):
return string
case .int(let int):
return "\(int)"
}
}
var intValue: Int? {
guard case let .int(int) = self else { return nil }
return int
}
init?(stringValue: String) {
guard !stringValue.isEmpty else { return nil }
self = .string(stringValue)
}
init?(intValue: Int) {
self = .int(intValue)
}
}
Related
I am trying to decode this type of JSON-Data in Swift
{"Total ingredients":[{"PE-LLD":"54.4 %"},{"PE-HD":"41.1 %"},{"TiO2":"4.5 %"}]}
The name and number of ingredients is variable. Therefore I am only able to decode it in this type of structure:
struct Product: Codable {
var total_ingredients: [[String: String]]?
private enum CodingKeys : String, CodingKey {
case total_ingredients = "Total ingredients"
}
}
But I would like to be able to decode it in either one dictionary: var total_ingredients: [String: String]? or my preferred choice in an array of objects: var total_ingredients: [Ingredient]?
struct Ingredient: Codable {
var name: String
var percentage: String
}
I already tried to solve my problem with an extension but it isn't working and I don't think that's the correct approach:
extension Ingredient {
init(_ ingredient: [String: String]) {
var key: String = ""
var value: String = ""
for data in ingredient {
key = data.key
value = data.value
}
self = .init(name: key, percentage: value)
}
}
Thanks in advance :)
You have to implement init(from decoder and map the array of dictionaries to Ingredient instances
struct Product: Decodable {
let totalIngredients: [Ingredient]
private enum CodingKeys : String, CodingKey { case totalIngredients = "Total ingredients" }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let ingredientData = try container.decode([[String:String]].self, forKey: .totalIngredients)
totalIngredients = ingredientData.compactMap({ dict -> Ingredient? in
guard let key = dict.keys.first, let value = dict[key] else { return nil }
return Ingredient(name: key, percentage: value)
})
}
}
struct Ingredient {
let name, percentage: String
}
let jsonString = """
{"Total ingredients":[{"PE-LLD":"54.4 %"},{"PE-HD":"41.1 %"},{"TiO2":"4.5 %"}]}
"""
let data = Data(jsonString.utf8)
do {
let result = try JSONDecoder().decode(Product.self, from: data)
print(result)
} catch {
print(error)
}
The extension is not needed.
I am attempting to read keys and values out of a JSON object that does not have a defined structure. The JSON looks similar to:
{
"content":"me,menu_cta,page",
"me": {
"email": "person#example.com",
"first_name": "Jordan"
},
"menu_cta": {
"menu_text": "Tap here"
},
"page": {
"how_it_works": "Make sure you're tapping the right spots.'",
"page_icon": "https://www.example.com/button.png",
"terms": "Terms and Conditions"
}
}
I don't think I can use Codeable since I'm unsure at compile-time what the keys will be inside the Dictionary and I'm also unsure if the values will be Strings or Dictionaries. The keys in the JSON that are read are dynamic based on user interaction.
I can start by setting up the Dictionary object:
let json = try? JSONSerialization.jsonObject(with: data, options: [])
if let dictionary = json as? [String: Any] {
self.prefetchDictionary = content
}
}
But when trying to read any data, ideally I would like to ask for page.how_it_works but I think the best method for doing this looks like this:
if let pageGenericDictionary = prefetchDictionary?["page"] {
if let pageDictionary = prefetchDictionary as? [String:String] {
headerText.text = pageDictionary["how_it_works"]
}
}
Is this the easiest way? Is there any method to write a simple function that could traverse down easily with definitions like page.how_it_works or me.email?
I am trying to avoid including a 3rd party library to achieve this.
First, how arbitrary is it? The above suggests that this is exactly [String: [String: String]], which is Decodable, and you should use JSONDecoder rather than JSONSerialization (you should generally avoid JSONSerialization):
let dict = try JSONDecoder().decode([String: [String: String]].self, from: json)
If the actual structure is arbitrary, then I suggest an arbitrary JSON decoder. Simplified version here (full version):
enum JSON: Codable {
struct Key: CodingKey, Hashable {
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 }
}
case string(String)
case number(Double) // FIXME: Split Int and Double
case object([Key: JSON])
case array([JSON])
case bool(Bool)
case null
init(from decoder: Decoder) throws {
if let string = try? decoder.singleValueContainer().decode(String.self) { self = .string(string) }
else if let number = try? decoder.singleValueContainer().decode(Double.self) { self = .number(number) }
else if let object = try? decoder.container(keyedBy: Key.self) {
var result: [Key: JSON] = [:]
for key in object.allKeys {
result[key] = (try? object.decode(JSON.self, forKey: key)) ?? .null
}
self = .object(result)
}
else if var array = try? decoder.unkeyedContainer() {
var result: [JSON] = []
for _ in 0..<(array.count ?? 0) {
result.append(try array.decode(JSON.self))
}
self = .array(result)
}
else if let bool = try? decoder.singleValueContainer().decode(Bool.self) { self = .bool(bool) }
else if let isNull = try? decoder.singleValueContainer().decodeNil(), isNull { self = .null }
else { throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [],
debugDescription: "Unknown JSON type")) }
}
func encode(to encoder: Encoder) throws {
switch self {
case .string(let string):
var container = encoder.singleValueContainer()
try container.encode(string)
case .number(let number):
var container = encoder.singleValueContainer()
try container.encode(number)
case .bool(let bool):
var container = encoder.singleValueContainer()
try container.encode(bool)
case .object(let object):
var container = encoder.container(keyedBy: Key.self)
for (key, value) in object {
try container.encode(value, forKey: key)
}
case .array(let array):
var container = encoder.unkeyedContainer()
for value in array {
try container.encode(value)
}
case .null:
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
subscript(key: String) -> JSON? {
guard let jsonKey = Key(stringValue: key),
case .object(let object) = self,
let value = object[jsonKey]
else { return nil }
return value
}
var stringValue: String? {
switch self {
case .string(let string): return string
default: return nil
}
}
var doubleValue: Double? {
switch self {
case .number(let number): return number
default: return nil
}
}
var intValue: Int? {
switch self {
case .number(let number): return Int(number)
default: return nil
}
}
subscript(index: Int) -> JSON? {
switch self {
case .array(let array): return array[index]
default: return nil
}
}
var boolValue: Bool? {
switch self {
case .bool(let bool): return bool
default: return nil
}
}
}
With that, you get the syntax:
let result = try JSONDecoder().decode(JSON.self, from: json)
let str: String? = result["page"]?["how_it_works"]?.stringValue
If you really want a dot-style syntax, you can get that with #dynamicMemberLookup:
#dynamicMemberLookup
enum JSON: Codable {
subscript(dynamicMember member: String) -> JSON {
return self[member] ?? .null
}
... the rest is the same ...
That would give you the syntax:
let x = result.page.how_it_works.stringValue
I got to know struct "Codable" in swift 4.0, *.
So, I tried that when decode josn.
if let jsonData = jsonString.data(using: .utf8) {
let decodingData = try? JSONDecoder().decode(SampleModel.self, from: jsonData)
}
Example sample data model below.
struct SampleModel : Codable {
var no: Int?
var category: Int?
var template_seq: Int?
}
And sample json data is .. below.
{
"data": {
"result" : 1
"total_count": 523,
"list": [
{
"no": 16398,
"category" : 23,
"template_seq" : 1
},
{
"no": -1,
"category" : 23,
"template_seq" : 1
}
]
}
}
But i want filtering wrong data.
If the value of "no" is less than or equal to 0, it is an invalid value.
Before not using codable...below.
(using Alamifre ison response )
guard let dictionaryData = responseJSON as? [String : Any] else { return nil }
guard let resultCode = dictionaryData["result"] as? Bool , resultCode == true else { return nil }
guard let theContainedData = dictionaryData["data"] as? [String:Any] else { return nil }
guard let sampleListData = theContainedData["list"] as? [[String : Any]] else { return nil }
var myListData = [MyEstimateListData]()
for theSample in sampleListData {
guard let existNo = theSample["no"] as? Int, existNo > 0 else {
continue
}
myListData.append( ... )
}
return myListData
how to filter wrong data or invalid data using swift 4.0 Codable ??
you can make codable for inital resonse
Here is your model:
import Foundation
struct Initial: Codable {
let data: DataModel?
}
struct DataModel: Codable {
let result, totalCount: Int
let list: [List]?
enum CodingKeys: String, CodingKey {
case result
case totalCount = "total_count"
case list
}
}
struct List: Codable {
let no, category, templateSeq: Int
enum CodingKeys: String, CodingKey {
case no, category
case templateSeq = "template_seq"
}
}
extension Initial {
init(data: Data) throws {
self = try JSONDecoder().decode(Initial.self, from: data)
}
}
And use it like that :
if let initail = try? Initial.init(data: data) , let list = initail.data?.list {
var myListData = list.filter { $0.no > 0 }
}
Yes, you have to use filters for that with codable:
1: Your struct should be according to your response like that:
struct SampleModel : Codable {
var result: Int?
var total_count: Int?
var list: [List]?
}
struct List : Codable {
var no: Int?
var category: Int?
var template_seq: Int?
}
2: Parse your response using a codable struct like that:
do {
let jsonData = try JSONSerialization.data(withJSONObject: dictionaryData["data"] as Any, options: JSONSerialization.WritingOptions.prettyPrinted)
let resultData = try JSONDecoder().decode(SampleModel.self, from: jsonData)
success(result as AnyObject)
} catch let message {
print("JSON serialization error:" + "\(message)")
}
3: now you can filter invalid data simply:
let filterListData = resultData.list?.filter({$0.no > 0})
let invalidData = resultData.list?.filter({$0.no <= 0})
Maybe someone in the community has had similar struggles and have come up with a workable solution.
We're currently working on a polyglot key/value store. Given this, we'll generally have no knowledge of what will be stored ahead of time.
Consider the following struct
struct Character : Codable, Equatable {
let name: String
let age: Int
let gender: Gender
let hobbies: [String]
static func ==(lhs: Character, rhs: Character) -> Bool {
return (lhs.name == rhs.name
&& lhs.age == rhs.age
&& lhs.gender == rhs.gender
&& lhs.hobbies == rhs.hobbies)
}
}
When sending/receiving Character entities over the wire, everything is fairly straight forward. The user can provide us the Type in which we can decode into.
However, we do have the ability to dynamically query the entities stored within the backend. For example, we can request the value of the 'name' property and have that returned.
This dynamism is a pain point. In addition to not knowing the type of the properties outside of the fact that they are Codable, the format that is returned can be dynamic as well.
Here's some examples of response for two different calls extracting properties:
{"value":"Bilbo"}
and
{"value":["[Ljava.lang.Object;",["Bilbo",111]]}
In some cases, it could be an equivalent of a dictionary.
Right now, I have the following structs for dealing with responses:
fileprivate struct ScalarValue<T: Decodable> : Decodable {
var value: T?
}
Using the Character example, the type passed to the decoder would be:
ScalarValue<Character>.self
However, for the single value, array, or dictionary case, I'm somewhat stuck.
I've started with something like:
fileprivate struct AnyDecodable: Decodable {
init(from decoder: Decoder) throws {
// ???
}
}
Based on the possible return types I've described above, I'm not sure if this is possible with the current API.
Thoughts?
Swift can definitely handle an arbitrary JSON decodable. This isn't the same thing as an arbitrary decodable. JSON can't encode all possible values. But this structure will decode anything that can be expressed in JSON, and from there you can explore it in a type-safe way without resorting to dangerous and awkward tools like Any.
enum JSON: Decodable, CustomStringConvertible {
var description: String {
switch self {
case .string(let string): return "\"\(string)\""
case .number(let double):
if let int = Int(exactly: double) {
return "\(int)"
} else {
return "\(double)"
}
case .object(let object):
return "\(object)"
case .array(let array):
return "\(array)"
case .bool(let bool):
return "\(bool)"
case .null:
return "null"
}
}
var isEmpty: Bool {
switch self {
case .string(let string): return string.isEmpty
case .object(let object): return object.isEmpty
case .array(let array): return array.isEmpty
case .null: return true
case .number, .bool: return false
}
}
struct Key: CodingKey, Hashable, CustomStringConvertible {
var description: String {
return stringValue
}
var hashValue: Int { return stringValue.hash }
static func ==(lhs: JSON.Key, rhs: JSON.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 }
}
case string(String)
case number(Double) // FIXME: Split Int and Double
case object([Key: JSON])
case array([JSON])
case bool(Bool)
case null
init(from decoder: Decoder) throws {
if let string = try? decoder.singleValueContainer().decode(String.self) { self = .string(string) }
else if let number = try? decoder.singleValueContainer().decode(Double.self) { self = .number(number) }
else if let object = try? decoder.container(keyedBy: Key.self) {
var result: [Key: JSON] = [:]
for key in object.allKeys {
result[key] = (try? object.decode(JSON.self, forKey: key)) ?? .null
}
self = .object(result)
}
else if var array = try? decoder.unkeyedContainer() {
var result: [JSON] = []
for _ in 0..<(array.count ?? 0) {
result.append(try array.decode(JSON.self))
}
self = .array(result)
}
else if let bool = try? decoder.singleValueContainer().decode(Bool.self) { self = .bool(bool) }
else {
self = .null
}
}
var objectValue: [String: JSON]? {
switch self {
case .object(let object):
let mapped: [String: JSON] = Dictionary(uniqueKeysWithValues:
object.map { (key, value) in (key.stringValue, value) })
return mapped
default: return nil
}
}
var arrayValue: [JSON]? {
switch self {
case .array(let array): return array
default: return nil
}
}
subscript(key: String) -> JSON? {
guard let jsonKey = Key(stringValue: key),
case .object(let object) = self,
let value = object[jsonKey]
else { return nil }
return value
}
var stringValue: String? {
switch self {
case .string(let string): return string
default: return nil
}
}
var doubleValue: Double? {
switch self {
case .number(let number): return number
default: return nil
}
}
var intValue: Int? {
switch self {
case .number(let number): return Int(number)
default: return nil
}
}
subscript(index: Int) -> JSON? {
switch self {
case .array(let array): return array[index]
default: return nil
}
}
var boolValue: Bool? {
switch self {
case .bool(let bool): return bool
default: return nil
}
}
}
With this, you can do things like:
let bilboJSON = """
{"value":"Bilbo"}
""".data(using: .utf8)!
let bilbo = try! JSONDecoder().decode(JSON.self, from: bilboJSON)
bilbo["value"] // "Bilbo"
let javaJSON = """
{"value":["[Ljava.lang.Object;",["Bilbo",111]]}
""".data(using: .utf8)!
let java = try! JSONDecoder().decode(JSON.self, from: javaJSON)
java["value"]?[1] // ["Bilbo", 111]
java["value"]?[1]?[0]?.stringValue // "Bilbo" (as a String rather than a JSON.string)
The proliferation of ? is somewhat ugly, but using throws on this doesn't really make the interface much nicer in my experiments (particularly because subscripts can't throw). Some tweaking may be advisable based on your particular use cases.
I wrote an AnyCodable struct myself for this purpose:
struct AnyCodable: Decodable {
var value: Any
struct CodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
init?(stringValue: String) { self.stringValue = stringValue }
}
init(value: Any) {
self.value = value
}
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(AnyCodable.self, forKey: key).value
}
value = result
} else if var container = try? decoder.unkeyedContainer() {
var result = [Any]()
while !container.isAtEnd {
result.append(try container.decode(AnyCodable.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"))
}
}
}
extension AnyCodable: Encodable {
func encode(to encoder: Encoder) throws {
if let array = value as? [Any] {
var container = encoder.unkeyedContainer()
for value in array {
let decodable = AnyCodable(value: value)
try container.encode(decodable)
}
} else if let dictionary = value as? [String: Any] {
var container = encoder.container(keyedBy: CodingKeys.self)
for (key, value) in dictionary {
let codingKey = CodingKeys(stringValue: key)!
let decodable = AnyCodable(value: value)
try container.encode(decodable, forKey: codingKey)
}
} else {
var container = encoder.singleValueContainer()
if let intVal = value as? Int {
try container.encode(intVal)
} else if let doubleVal = value as? Double {
try container.encode(doubleVal)
} else if let boolVal = value as? Bool {
try container.encode(boolVal)
} else if let stringVal = value as? String {
try container.encode(stringVal)
} else {
throw EncodingError.invalidValue(value, EncodingError.Context.init(codingPath: [], debugDescription: "The value is not encodable"))
}
}
}
}
It works with nested dictionaries/arrays too. You can try it with any json in a playground.
let decoded = try! JSONDecoder().decode(AnyCodable.self, from: jsonData)
Yes, is possible to achieve what you described via the existing Codable API, and in an elegant manner I'd say (though I might be subjective here since I'm talking about my code :) ).
Let try to figure out what is needed for this task:
First things first, you need to declare all properties as optional. This is needed as the decoder is likely to have to deal with partial responses.
struct Character: Codable {
let name: String?
let age: Int?
let hobbies: [String]?
}
Next, we need a way to figure out how to map the struct properties to the various fields from the partial JSONs. Luckily the Codable API can help us here via the CodingKeys enum:
enum CodingKeys: String, CodingKey {
case name
case age
case hobbies
}
The first tricky part is to somehow convert the CodingKeys enum into an array of strings, that we can use for the array response - {"value":["[Ljava.lang.Object;",["Bilbo",111]]}. We are in luck here, there are various sources on the internet and SO that address the question of getting all cases of an enum. My preferred solutions is the RawRepresentable extension, since CodingKey is raw representable and it's raw value is a String:
// Adds support for retrieving all enum cases. Since we refer a protocol here,
// theoretically this method can be called on other types than enum
public extension RawRepresentable {
static var enumCases: [Self] {
var caseIndex: Int = 0
return Array(AnyIterator {
defer { caseIndex += 1 }
return withUnsafePointer(to: &caseIndex) {
$0.withMemoryRebound(to: Self.self, capacity: 1) { $0.pointee }
}
})
}
}
We're almost there, but we need some more work before we can decode.
Now that we have a Decodable type, a list of coding keys to use, we need a decoder that makes use of these. But before that, we need to be able to recognise types that can be partially decoded. Let's add a new protocol
protocol PartiallyDecodable: Decodable {
associatedtype PartialKeys: RawRepresentable
}
and make Character conform to it
struct Character : Codable, PartiallyDecodable {
typealias PartialKeys = CodingKeys
The finishing piece is the decoding part. We can reuse the JSONDecoder that comes with the standard library:
// Tells the form of data the server sent and we want to decode:
enum PartialDecodingStrategy {
case singleKey(String)
case arrayOfValues
case dictionary
}
extension JSONDecoder {
// Decodes an object by using a decoding strategy
func partialDecode<T>(_ type: T.Type, withStrategy strategy: PartialDecodingStrategy, from data: Data) throws -> T where T : PartiallyDecodable, T.PartialKeys.RawValue == String {
Connecting all of the above results in the following infrastructure:
// Adds support for retrieving all enum cases. Since we refer a protocol here,
// theoretically this method can be called on other types than enum
public extension RawRepresentable {
static var enumCases: [Self] {
var caseIndex: Int = 0
return Array(AnyIterator {
defer { caseIndex += 1 }
return withUnsafePointer(to: &caseIndex) {
$0.withMemoryRebound(to: Self.self, capacity: 1) { $0.pointee }
}
})
}
}
protocol PartiallyDecodable: Decodable {
associatedtype PartialKeys: RawRepresentable
}
// Tells the form of data the server sent and we want to decode:
enum PartialDecodingStrategy {
case singleKey(String)
case arrayOfValues
case dictionary
}
extension JSONDecoder {
// Decodes an object by using a decoding strategy
func partialDecode<T>(_ type: T.Type, withStrategy strategy: PartialDecodingStrategy, from data: Data) throws -> T where T : PartiallyDecodable, T.PartialKeys.RawValue == String {
guard let partialJSON = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [AnyHashable:Any] else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Invalid JSON"))
}
guard let value = partialJSON["value"] else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Missing \"value\" key"))
}
let processedJSON: [AnyHashable:Any]
switch strategy {
case let .singleKey(key):
processedJSON = [key:value]
case .arrayOfValues:
guard let values = value as? [Any],
values.count == 2,
let properties = values[1] as? [Any] else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Invalid JSON: expected a 2 elements array for the \"value\" key"))
}
processedJSON = zip(T.PartialKeys.enumCases, properties)
.reduce(into: [:]) { $0[$1.0.rawValue] = $1.1 }
case .dictionary:
guard let dict = value as? [AnyHashable:Any] else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Invalid JSON: expected a dictionary for the \"value\" key"))
}
processedJSON = dict
}
return try decode(type, from: JSONSerialization.data(withJSONObject: processedJSON, options: []))
}
}
We want to be able to partially decode Character, so we make it adopt all the required protocols:
struct Character: Codable, PartiallyDecodable {
typealias PartialKeys = CodingKeys
let name: String?
let age: Int?
let hobbies: [String]?
enum CodingKeys: String, CodingKey {
case name
case age
case hobbies
}
}
Now the fun part, let's test it:
let decoder = JSONDecoder()
let jsonData1 = "{\"value\":\"Bilbo\"}".data(using: .utf8)!
print((try? decoder.partialDecode(Character.self,
withStrategy: .singleKey(Character.CodingKeys.name.rawValue),
from: jsonData1)) as Any)
let jsonData2 = "{\"value\":[\"[Ljava.lang.Object;\",[\"Bilbo\",111]]}".data(using: .utf8)!
print((try? decoder.partialDecode(Character.self,
withStrategy: .arrayOfValues,
from: jsonData2)) as Any)
let jsonData3 = "{\"value\":{\"name\":\"Bilbo\",\"age\":111,\"hobbies\":[\"rings\"]}}".data(using: .utf8)!
print((try? decoder.partialDecode(Character.self,
withStrategy: .dictionary,
from: jsonData3)) as Any)
As we might expect, the output is the following:
Optional(MyApp.Character(name: Optional("Bilbo"), age: nil, hobbies: nil))
Optional(MyApp.Character(name: Optional("Bilbo"), age: Optional(111), hobbies: nil))
Optional(MyApp.Character(name: Optional("Bilbo"), age: Optional(111), hobbies: Optional(["rings"])))
As we can see, with the proper infrastructure laid out, the only requirements for a type to be partially decodable is to conform to PartiallyDecodable and to have an enum that says which keys to decode. These requirements are easy to be followed.
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
}
}
}
}