Codable: decoding by key - swift

Suppose I have an API that returns this json:
{
"dogs": [{"name": "Bella"}, {"name": "Lucy"}],
"cats": [{"name": "Oscar"}, {"name": "Coco"}]
}
And a model that looks like this:
import Foundation
public struct Animal: Codable {
let name: String?
}
Now I want to decode the array of Animal from the "dogs" key:
let animals = try JSONDecoder().decode([Animal].self, from: response.data!)
However, I somehow have to reference the "dogs" key. How do I do this?

First of all, the JSON you provided is not valid JSON. So let's assume that what you actually mean is this:
{
"dogs": [{"name": "Bella"}, {"name": "Lucy"}],
"cats": [{"name": "Oscar"}, {"name": "Coco"}]
}
Then the problem with your code is merely this line:
let animals = try JSONDecoder().decode([Animal].self, from: response.data!)
You're claiming that the JSON represents an array of Animal. But it doesn't. It represents a dictionary with keys dogs and cats. So you just say so.
struct Animal: Codable {
let name: String
}
struct Animals: Codable {
let dogs: [Animal]
let cats: [Animal]
}
Now everything will just work:
let animals = try JSONDecoder().decode(Animals.self, from: response.data!)

You can all get all values from JSON like this:
let arrayOfResponse = Array(response.data.values)
let clinicalTrial = try JSONDecoder().decode([Animal].self, from: arrayOfResponse!)

if you know keys previously like dogs, cats you can do like this
struct Initial: Codable {
let dogs, cats: [Animal]
}
struct Animal: Codable {
let name: String
}
// MARK: Convenience initializers
extension Initial {
init(data: Data) throws {
self = try JSONDecoder().decode(Initial.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
}
// data is your response data
if let inital = try? Initial.init(data: data) {
let cats = inital.cats
let dogs = inital.dogs
}

Your JSON is slightly off, you will have to put double quotes around your name, but that way you can run the following Playground:
import Cocoa
let jsonData = """
{
"dogs": [{"name": "Bella"}, {"name": "Lucy"}],
"cats": [{"name": "Oscar"}, {"name": "Coco"}]
}
""".data(using: .utf8)!
public struct Animal: Codable {
let name: String
}
do {
let anims = try JSONDecoder().decode([String:[Animal]].self, from:jsonData)
print(anims)
for kind in anims.keys {
print(kind)
if let examples = anims[kind] {
print(examples.map {exa in exa.name })
}
}
} catch {
print(error)
}
This will not restrict you to cats and dogs, but it is usually a bad idea to use "unknown" keys as data elements in a hash. If you can modify your JSON (which you should since it is not very well structured anyways) you could also move the "kind" of animals to some data element in an array of hashes which will be much more flexible.

Related

How to declare a multidimensional String in Realm?

This bounty has ended. Answers to this question are eligible for a +50 reputation bounty. Bounty grace period ends in 13 hours.
Dipesh Pokhrel wants to draw more attention to this question:
I have gone throught the documentation no where its specifically mentioned. how to deal with the multidimensional string objects
I have a realm class which contains the a multi dimensional string, realm in Decodable is throwing an error to while parsing, created class to support realm.
class Categories : Object,Decodable {
// var assetSum : [[String]]? // In swift originally
let assetSum = RealmSwift.List<String>() // modified to support list
#objc var id : String?
#objc var dn : String?
How to fix this , to be more Generalise how to store var assetSum : [[String]]? this kind of value in realm?
I have gone through the documentation of realm but could not find something related to this
Realm supports basic types like Int, String, Date etc. and several collections types like List (Array), Map (Dictionary) from the box. For the other your custom types you can use json serialization which works pretty quick.
It can be implemented with two variables where persistent private one is for storing data and public one is for accessing e.g:
import RealmSwift
class Categories: Object {
#Persisted private var assetSum: Data?
var assetSumValue: [[String]]? {
get {
guard let value = assetSum else {
return nil
}
return try? JSONDecoder().decode(([[String]]?).self, from: value)
}
set {
assetSum = try? JSONEncoder().encode(newValue)
}
}
}
Now you can easy set/get values with assetSumValue:
// Create and save
let categories = Categories()
try realm.write {
categories.assetSumValue = [["1", "2", "3"], ["4", "5", "6"]]
realm.add(categories)
}
// Get first element from DB
if let categories = realm.objects(Categories.self).first,
let value = categories.assetSumValue
{
print(value) // Prints: [["1", "2", "3"], ["4", "5", "6"]]
}
In case of encoding/decoding your custom Realm types with complex properties you should implement a custom decoder:
class Categories: Object, Codable {
#Persisted var id: String?
#Persisted var dn: String?
#Persisted private var assetSum: Data?
var assetSumValue: [[String]]? {
get {
guard let value = assetSum else {
return nil
}
return try? JSONDecoder().decode(([[String]]?).self, from: value)
}
set {
assetSum = try? JSONEncoder().encode(newValue)
}
}
override init() {
super.init()
}
required init(from decoder: Decoder) throws {
super.init()
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode((String?).self, forKey: .id)
dn = try values.decode((String?).self, forKey: .dn)
assetSumValue = try values.decode(([[String]]?).self, forKey: .assetSum)
}
}
How to decode:
let json = """
{
"id": "100",
"dn": "200",
"assetSum": [
["one", "two", "three"],
["four", "five", "six"]
]
}
"""
let categories = try JSONDecoder().decode(Categories.self, from: json.data(using: .utf8)!)
if let value = categories.assetSumValue {
print(value) // Prints [["one", "two", "three"], ["four", "five", "six"]]
}

Decode json object in Swift

I have a json file and reading it and decode it as follows.
my json file looks like as follows, A and B represents the struct object. I wonder are there a better and effective way to decoding this type of json?
[
[
{"id": "152478", "age": "20"},{"character": "king", "isDead":"no", "canMove" :"yes"}
],
[
{"id": "887541", "age": "22"},{"character": "lion", "isDead":"no", "canMove" :"yes"}
]
]
decoding is as follows:
let url = Bundle.main.url(forResource: "mypew", withExtension: "json")!
do {
let jsonData = try Data(contentsOf: url)
// B -> json[0][0], [1][0]
// A -> json[0][1], [0][1]
response = try JSONSerialization.jsonObject(with: jsonData) as! [[[String: Any]]]
for i in 0..<response.count
{
for j in 0..<response[i].count
{
if j == 0
{
let jsonDat = (try? JSONSerialization.data(withJSONObject:response[i][j]))!
let bElement = try JSONDecoder().decode(B.self, from: jsonDat)
self.bArray.append(bElement)
}
else if j == 1
{
let jsonDatt = (try? JSONSerialization.data(withJSONObject:response[i][j]))!
let aElement = try JSONDecoder().decode(A.self, from: jsonDatt)
self.aArray.append(aElement)
}
}
}
First, make the two objects (let's call them A and B) conform to Decodable:
struct A: Decodable {
var id, age: String
}
struct B: Decodable {
var character, isDead, canMove: String
}
Then, if your structure for the pair of A and B is always the same, i.e. it's always [A, B], then you can decode the pair into its own object. Let's call that object ABPair:
struct ABPair: Decodable {
let a: A
let b: B
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
guard let a = try? container.decode(A.self),
let b = try? container.decode(B.self)
else {
// throw since we didn't find A first, followed by B
throw DecodingError.dataCorruptedError(in: container, debugDescription: "error")
}
self.a = a
self.b = b
}
}
And you can decode the array of pairs as follows:
let decoder = JSONDecoder()
let pairs = try? decoder.decode([ABPair].self, from: jsonData)
print(pairs[1].b.character) // lion
Firstly, you need to create a Codable struct for the model. Then decode the model from the data and not the JSON object. Here the code,
Model:
struct Model: Codable {
let id, age, character, isDead, canMove: String?
}
Decoding:
let url = Bundle.main.url(forResource: "mypew", withExtension: "json")!
do {
let jsonData = try Data(contentsOf: url)
let models = try JSONDecoder().decode([[Model]].self, from: jsonData)
for model in models {
print(model[0].age, model[1].canMove)
}
} catch {
print(error)
}

JSONDecoder using Protocol

I'm using a protocol to create several structs which I use to decode using JSONDecoder. Here's a code sample of what I'm trying to achieve.
protocol Animal: Codable
{
var name: String { get }
var age: Int { get }
}
struct Dog: Animal
{
let name: String
let age: Int
let type: String
}
struct Cat: Animal
{
let name: String
let age: Int
let color: String
}
Here are the seperate JSON payloads of dog and cat:
{
"name": "fleabag",
"age": 3,
"type": "big"
}
{
"name": "felix",
"age": 2,
"color": "black"
}
So when I decode the JSON, I'm not sure what JSON I'll have, dog or cat. I tried doing this:
let data = Data(contentsOf: url)
let value = JSONDecoder().decode(Animal.self, from: data)
But end up with this error:
In argument type 'Animal.Protocol', 'Animal' does not conform to expected type 'Decodable'
Any ideas as to the best approach to parse either dog or cat returning an instance of Animal?
Thanks
You're not going to be able to use this:
let animal = try? JSONDecoder().decode(Animal.self, from: data)
To decode a Dog or a Cat. It's always going to be an Animal.
If you want to decode both those JSON objects to Animal, then define Animal like this:
struct Animal: Codable {
var name: String
var age: Int
}
Of course, you'll lose the distinctive elements that make them a Dog (type) or Cat (color).
Your opening a somewhat ugly can of worms here. I understand what you try to do, but unfortunately it fails in a number of ways. You can get somewhat close to what you want with the following Playground:
import Cocoa
let dogData = """
{
"name": "fleabag",
"age": 3,
"type": "big"
}
""".data(using: .utf8)!
let catData = """
{
"name": "felix",
"age": 2,
"color": "black"
}
""".data(using: .utf8)!
protocol Animal: Codable
{
var name: String { get }
var age: Int { get }
}
struct Dog: Animal
{
let name: String
let age: Int
let type: String
}
struct Cat: Animal
{
let name: String
let age: Int
let color: String
}
do {
let decoder = JSONDecoder()
let dog = try decoder.decode(Dog.self, from: dogData)
print(dog)
let cat = try decoder.decode(Cat.self, from: catData)
print(cat)
}
extension Animal {
static func make(fromJSON data: Data) -> Animal? {
let decoder = JSONDecoder()
do {
let dog = try decoder.decode(Dog.self, from: data)
return dog
} catch {
do {
let cat = try decoder.decode(Cat.self, from: data)
return cat
} catch {
return nil
}
}
}
}
if let animal = Dog.make(fromJSON: dogData) {
print(animal)
}
if let animal2 = Dog.make(fromJSON: catData) {
print(animal2)
}
However you will notice that there are some changes that do have a reason. As a matter of fact you cannot implement the Decodable method init(from: Decoder) throws since it is supposed to chain to the init method which ... does not really work out for a protocol. I chose instead to implement your favourite dispatcher in the Animal.make method, but this ended up as a half baked solution as well. Since protocols are metatypes (probably for a good reason as well) you are not able to call their static methods on the metatype and have to use a concrete one. As the line Dog.make(fromJSON: catData) shows this looks weird to say the least. It would be better to bake this into a top level function such as
func parseAnimal(from data:Data) {
...
}
but still this looks unsatisfactory in another way since it pollutes the global namespace. Probably still the best we can do with the means available.
Given the ugliness of the dispatcher it seems like a bad idea to have JSON with no direct indication of the type since it makes parsing really hard. However, I do not see a nice way to communicate a subtype in JSON in a way that really makes it easy to parse. Have not done any research on this, but it might be your next try.
A better approach would be to use a class instead of a protocol and use classes instead of structs. Your Dog and Cat classes will be subclasses of Animal
class Animal: Codable {
let name: String
let age: Int
private enum CodingKeys: String, CodingKey {
case name
case age
}
}
class Dog: Animal {
let type: String
private enum CodingKeys: String, CodingKey {
case type
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.type = try container.decode(String.self, forKey: .type)
try super.init(from: decoder)
}
}
class Cat: Animal {
let color: String
private enum CodingKeys: String, CodingKey {
case color
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.color = try container.decode(String.self, forKey: .color)
try super.init(from: decoder)
}
}
let data = Data(contentsOf: url)
let animal = JSONDecoder().decode(Animal.self, from: data)

Parse complex json code

I have the following JSON code and want to parse it in Swift. I use Alamofire to get the JSON and have created a struct for the parsing:
{
"-8802586561990153106-1804221538-5":{
"zug":{
"klasse":"RB",
"nummer":"28721"
},
"ankunft":{
"zeitGeplant":"1804221603",
"zeitAktuell":"1804221603",
"routeGeplant":[
"Wiesbaden Hbf",
"Mainz Hbf"
]
},
"abfahrt":{
"zeitGeplant":"1804221604",
"zeitAktuell":"1804221604",
"routeGeplant":[
"Gro\u00df Gerau",
"Klein Gerau",
"Weiterstadt"
]
}
},
"8464567322535526441-1804221546-15":{
"zug":{
"klasse":"RB",
"nummer":"28724"
},
"ankunft":{
"zeitGeplant":"1804221657",
"zeitAktuell":"1804221708",
"routeGeplant":[
"Aschaffenburg Hbf",
"Mainaschaff"
]
},
"abfahrt":{
"zeitGeplant":"1804221658",
"zeitAktuell":"1804221709",
"routeGeplant":[
"Mainz-Bischofsheim"
]
}
}
}
I have created a struct for this that looks like this:
struct CallResponse: Codable {
struct DirectionTrain: Codable {
struct Train: Codable {
let trainClass: String
let trainNumber: String
}
struct Arrival: Codable {
let line: String
let eta: Date
let ata: Date
let platform: String
let route: [String]
}
struct Departure: Codable {
let line: String
let etd: Date
let atd: Date
let platform: String
let route: [String]
}
}
}
The rest of my code is:
Alamofire.request(url!).responseJSON { response in
switch response.result {
case .success:
let decoder = JSONDecoder()
let parsedResult = try! decoder.decode(CallResponse.self, from: response.data!)
case .failure(let error):
print(error)
}
}
When I run this code the error message is:
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "train", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"train\", intValue: nil) (\"train\").", underlyingError: nil))
Can anyone help me find my problem? Thank you for your answers!
The problem is merely that your structs look nothing at all like your JSON!
Your JSON is a dictionary whose keys have names like "-8802586561990153106-1804221538-5" and "8464567322535526441-1804221546-15". But I don't see you declaring any struct that deals with those keys.
Then each of those turns out to be a dictionary with keys like "zug", "ankunft", and "abfahrt". But I don't see you declaring any struct that deals with those keys either.
And then the "zug" has keys "klasse" and "nummer"; you don't have those either.
And so on.
Either your structs must look exactly like your JSON, or else you must define CodingKeys and possibly also implement init(from:) to deal with any differences between your structs and your JSON. I suspect that the keys "-8802586561990153106-1804221538-5" and "8464567322535526441-1804221546-15" are unpredictable, so you will probably have to write init(from:) in order to deal with them.
For example, I was able to decode your JSON like this (I do not really recommend using try!, but we decoded without error and it's just a test):
struct Entry : Codable {
let zug : Zug
let ankunft : AnkunftAbfahrt
let abfahrt : AnkunftAbfahrt
}
struct Zug : Codable {
let klasse : String
let nummer : String
}
struct AnkunftAbfahrt : Codable {
let zeitGeplant : String
let zeitAktuell : String
let routeGeplant : [String]
}
struct Top : Decodable {
var entries = [String:Entry]()
init(from decoder: Decoder) throws {
struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
let con = try! decoder.container(keyedBy: CK.self)
for key in con.allKeys {
self.entries[key.stringValue] =
try! con.decode(Entry.self, forKey: key)
}
}
}
// d is a Data containing your JSON
let result = try! JSONDecoder().decode(Top.self, from: d)

swift4 JSONDecoder. How to decode an array with different type inside?

JSON to decode:
{
"jsonrpc": "2.0",
"result": [
{
"code": 1,
"message": "error"
},
[
{
"gid": "123"
....
}
]
....
]
}
"JSONSerialization" is complex to decode this json.
let str = """
{"jsonrpc": "2.0","result": [{"code": 1,"message": "error"},[{"gid": "123"}]]}
"""
let data = str.data(using: .utf8)!
if let json = try! JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any],
let result = json["result"] as? [Any] {
let error = result.map { $0 as? [Any] }.filter { $0 != nil }
let objs = result.map { $0 as? [String: Any] }.filter { $0 != nil }
print(error)
print(objs)
}
Is there a way to decode the JSON payload with JSONDecoder to [Data] or anything else.
struct Result: Codable {
let result: [???] //can't use [Data] here
}
Is there a way to decode the JSON payload with JSONDecoder to [Data] or anything else.
The decode method on JSONDecoder requires some type that conforms to JSONDecodable. True, Any does not conform to Decodable and neither does Data or [Data]. You probably want to define a custom Swift type to represent your JSON and make that conform to Decodable.
How to decode an array with different type inside?
The reason we want to see the complete JSON is that it is useful to know whether the dictionaries and arrays inside result are consistent. If the entries are all like your example, then perhaps you could do something like this:
struct JSONRPC: Decodable {
enum Result: Decodable {
struct ArrayItem: Decodable {
let code: Int
let message: String
}
case array([ArrayItem])
case dictionary(Dictionary<String, String>)
init(from decoder: Decoder) throws {
// Implement decoder for enum with associated values.
// This will not "just work" without your additional
// instructions specifying how to do it. But that's an
// answer for a separate question, I think.
}
}
let version: String
let results: [Result]
private enum CodingKeys: String, CodingKey {
case version = "jsonrpc"
case results = "result"
}
}
With that in place, you can use it like this:
let decoder = JSONDecoder()
let payload = try decoder.decode(JSONRPC.self, from: data)
If the decoding succeeds, then payload will be an instance of RPCJSON.