How do I properly decode this json string using decodable? - ios

I have the following json string:
{"weight":[{"bmi":24.75,"date":"2020-01-20","logId":1000,"source":"API","time":"23:59:59","weight":200}]}
I want to convert it to a Swift object in order to access the different values. Here is what I am trying to do, I have these structs setup:
struct FitbitResponseModel: Decodable {
let weight: [FitbitResponseData]
}
struct FitbitResponseData: Decodable {
let bmi: Int
let date: String
let logId: Int
let source: String
let time: String
let weight: Int
}
And then I have this method to decode the json string:
func parseJSON(data: Data) -> FitbitResponseModel? {
var returnValue: FitbitResponseModel?
do {
returnValue = try JSONDecoder().decode(FitbitResponseModel.self, from: data)
} catch {
print("Error took place: \(error.localizedDescription).")
}
return returnValue
}
However when I try to run it I get the error that the data couldn’t be read because it isn’t in the correct format. What am I doing wrong? Any help is appreciated.
Thanks in advance!

change
let bmi: Int
to
let bmi: Double
beacuse it's value is coming out to be 24.75 in your response if any variable type doesn't match to JSON response whole model wouldn't map in Codable protocol (Encodable and Decodable)

Talk to your API developer. 000 is not a valid representation of a number for json. It needs to be either 0 or 0.0. You can lint your json at https://jsonlint.com . If you really need to work around this I suggest doing a string replacement on 000, with 0, before you parse the data.

Json is n't valid because logId value in your json is n't valid.
{
"weight": [{
"bmi": 24.75,
"date": "2020-01-20",
"logId": 100,
"source": "API",
"time": "23:59:59",
"weight": 200
}]
}

One really neat feature of this auto-generated conformance is that if you define an enum in your type called "CodingKeys" (or use a type alias with this name) that conforms to the CodingKey protocol – Swift will automatically use this as the key type. This therefore allows you to easily customise the keys that your properties are encoded/decoded with.
struct Base: Codable {
let weight : [Weight]?
enum CodingKeys: String, CodingKey {
case weight = "weight"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
weight = try values.decodeIfPresent([Weight].self, forKey: .weight)
}
}
struct Weight : Codable {
let bmi : Double?
let date : String?
let logId : Int?
let source : String?
let time : String?
let weight : Int?
enum CodingKeys: String, CodingKey {
case bmi = "bmi"
case date = "date"
case logId = "logId"
case source = "source"
case time = "time"
case weight = "weight"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
bmi = try values.decodeIfPresent(Double.self, forKey: .bmi)
date = try values.decodeIfPresent(String.self, forKey: .date)
logId = try values.decodeIfPresent(Int.self, forKey: .logId)
source = try values.decodeIfPresent(String.self, forKey: .source)
time = try values.decodeIfPresent(String.self, forKey: .time)
weight = try values.decodeIfPresent(Int.self, forKey: .weight)
}
}
Hope that will help!
or you can use SwiftyJSON lib: https://github.com/SwiftyJSON/SwiftyJSON

Related

Swift5 : How to handle this JSON response into Swift Data Model

I am very new to Swift, so please pardon me if it is a silly question
How to handle this type of response into data model ,response is in this form:
{
"id" = 1;
count = "";
data = "[{"key":"value","key":"value".......}]";
message = SUCCESS;
"response_code" = 1;
}
The AlamofireRequest gets the response and prints it but when using responseDecodable,nothing happens, the code of Alamofire request is below:
let request = AF.request(urlString!,method: .get)
request.responseJSON { (data) in
print(data)
}
request.responseDecodable(of: Test.self) { (response) in
guard let getData = response.value else {return}
print(getData.all[0].firstName) //Nothing prints
}
And this is how the data model looks like:
struct Test: Decodable {
let firstName: String
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
//firstname is inside data of json
}
}
struct Getdata: Decodable {
let all : [Test]
enum CodingKeys: String, CodingKey {
case all = "data"
}
}
Want to access values inside data and print it. Please shed some light on it!
Trying to address both the question and the comments above, the first thing would be to get some valid JSON. It could be that your API is providing non-JSON data, in which case neither this answer or using AlamoFire to decode it will work. However, formatting the above as JSON, at my best guess of what it's meant to be, will give:
let json = """
{
"id": 1,
"count": "",
"data": [
{
"key1": "value1",
"key2": "value2"
}
],
"message": "SUCCESS",
"response_code": 1
}
"""
At this point it's more obvious that the content under the data key is not an array of Test as defined above, but is an array of dictionaries (with a single enry in the array in the example). So it will be necessary to respecify the data model. For something this simple, for the use case mentioned, there is no real reason to go for multiple types, so we're going to redefine Getdata (I'm sticking with your naming despite not liking it):
struct Getdata: Decodable {
let all : [[String:String]]
enum CodingKeys: String, CodingKey {
case all = "data"
}
}
To test the decoding let's used the standard JSONDecoder (I don't have a Playground handy with AlamoFire as I'd never use it):
do {
let wrapper = try JSONDecoder().decode(Getdata.self, from: Data(json.utf8))
print(wrapper.all)
} catch {
print("Decoding error \(error.localizedDescription)")
}
This outputs
[["key1": "value1", "key2": "value2"]]
as would be expected.
Now there's a valid JSON decoding solution it should be easy to drop this into the AlamoFire APIs if you so wish. Although if the backend really is providing the misformed JSON as in question this won't work and you'll have to change the backend, decode it with your own decoder or mangle it into valid JSON.
Did lot of silly mistakes in original question, long way to go
As suggested by #flanker I did respecify the data model to
struct Test : Codable {
let id : String?
let message : String?
let data : String?
let count : String?
let response_code : String?
enum CodingKeys: String, CodingKey {
case id = "$id"
case message = "message"
case data = "data"
case count = "count"
case response_code = "response_code"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(String.self, forKey: .id)
message = try values.decodeIfPresent(String.self, forKey: .message)
data = try values.decodeIfPresent(String.self, forKey: .data)
count = try values.decodeIfPresent(String.self, forKey: .count)
response_code = try values.decodeIfPresent(String.self, forKey: .response_code)
}
And now it seems to work
Now getting the desired output
[["key1": "value1", "key2": "value2"]]

Swift Decodable parse part of JSON

I'm trying to parse a json that looks like this using decodable:
{
"count": 1,
"results": [
{
"title": 1,
"content": "Bla"
} ]
}
My problem is that I don't want to make a class that has a count property just to be able to use the decoder. I want to parse only the results part I don't care about the count.
So my question is, can decodable.decode somehow only parse a part of the result json. I mean a certain key path instead of the whole json ? And I want to do it using Decodable.
In a nutshell I don't want this:
class IncidentWrapper: Codable{
var count: Int
var incident: [Incident]
}
What I would Imagine is to have this:
decodable.decode([Incident].self, from: response.data, forKey: "results")
Thanks
let me see what I can suggest:
struct Result: Codeable {
var id: Int
var message: String
var color: String
var type: String
enum CodingKeys: String, CodingKey {
case results
}
enum NestedResultKeys: String, CodingKey {
case id, message, color, type
}
}
extension Result: Decodable {
init(from decoder: Decoder) throws {
let result = try decoder.container(keyedBy: CodingKeys.self)
let nestedResult = try result.nestedContainer(keyedBy: NestedResultKeys.self, forKey: .result)
id = try nestedResult.decode(Int.self, forKey: .id)
message = try nestedResult.decode(String.self, forKey: .message)
color = try nestedResult.decode(String.self, forKey: .color)
type = try nestedResult.decode(String.self, forKey: .id)
}
}
See this documentation for more insight
https://developer.apple.com/documentation/swift/swift_standard_library/encoding_decoding_and_serialization
Hope it helps your project!
You probably is looking for JSONSerialization class. This is an example how it works:
if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] {
if let results = json["results"] as? [Incident] {
print(results.count)
}
}
You can define a generic wrapper for once and use everywhere.
It will work as a generic wrapper for results key only.
protocol ResultsDecodable: Decodable{
associatedtype T: Decodable
var results: [T] {get}
}
struct Result<Element: Decodable>: ResultsDecodable{
typealias T = Element
var results: [Element]
}
Extend JSONDecoder to get results output.
extension JSONDecoder {
func resultDecode<T>(_ type: Result<T>.Type, from data: Data) throws -> [T] where T : Decodable{
let model = try! decode(type, from: data)
return model.results
}
}
And use like this
var str = #"{"count": 1,"results": [{"title": 1,"content": "Bla"}, {"title": 2,"content": "Bla"} ]}"#
class Incident: Decodable{
let title: Int
let content: String
}
let indicents = (try! JSONDecoder().resultDecode(Result<Incident>.self, from: str.data(using: .utf8)!))
See how it makes everything more complex. BETTER USE IncidentWrapper!!!
You only need to use the keys you care about. Just leave off the count. Don't make it part of your struct.
You will only get errors if you can't find a key in the json that you are expecting in the struct. You can avoid this too, though, if you make it an optional in the struct.

Decoding JSON and map it to existing object Swift

I have the following object
struct Properties: Decodable {
var id: String?
var value: String?
var color: String?
}
In the first request to server I get the following response
{
"id":"1",
"color":"red"
}
And after another request I get
{
"id":"1", // the id of the object props is meant for
"props":{
"value":"my value" // I can get any property here
}
}
After the two requests I should have the object with all properties set
By now I decode the second request as following
struct SetAttr: Decodable {
let id: String
let props: [String : Any]
enum SetAttrCodingKeys: String, CodingKey {
case id
case props
}
init(from decoder: Decoder) throws {
let container = try! decoder.container(keyedBy: SetAttrCodingKeys.self)
props = try! container.decode([String : Any].self, forKey: .props)
id = try! container.decode(String.self, forKey: .id)
}
}
But I do not know how to parse props dictionary and set the properties on the first object. I am willing to use a decoding library, but I did not find any that can do this
EDIT:
This is how I tried to set the properties from dictionary, but the solution is not scalable
var myObject: Properties
properties = setAttr.props // [String:Any]
let keys = properties.keys
keys.forEach { key in
if let value = properties[key] {
switch key {
case "value":
myObject.value = value as? String
case "color":
myObject.color = value as? String
default:
break
}
}
}
Just use JSONSerialization which parses whatever you throw at it into arrays and dictionaries. That frees you from all the problems you have with strangely formatted JSON.
For example, the second request will be parsed as a dictionary with two keys "id" and "props", and "props" has a value which is again a dictionary with one key "value" and a value "my value".
And please stop using try! That will cause your app to crash instantly if any input is not expected. Unexpected inputs should be handled, not lead to a crash.
There are various way to do this, but one possible way could be something like this:
struct SecAttr: Decodable {
let id: String
var props: Properties?
private enum CodingKeys: String, CodingKey {
case id
case props
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
if let props = try container.decodeIfPresent(Properties.self, forKey: .props) {
self.props = props
} else {
// decode Properties from the same object
self.props = try Properties(from: decoder)
}
}
}
struct Properties: Decodable {
var value: String?
var color: String?
mutable update(from props: Properties) {
value = props.value ?? value
color = color.value ?? color
}
}
Now you can decode your original object and after getting updated properties, just update them on the original.

Parsing nested unkeyed JSON with Codable

I am trying to parse an array of heterogeneous objects using Codable. These objects are also unkeyed as well. I should note that I have the container structure correct, because it DOES loop through and print "it is type1" at all correct times as seen below. I just can't figure out how to access the actual object. Here is my code:
var data: [Any]
public init(from decoder: Decoder) throws {
var container = try! decoder.container(keyedBy: CodingKeys.self).nestedUnkeyedContainer(forKey: .data)
while !container.isAtEnd {
var itemContainer = try container.nestedContainer(keyedBy: CodingKeys.self)
let itemType = try itemContainer.decode(String.self, forKey: .type)
switch itemType {
case "type1":
print("it is type1")
// this does not compile, but is what I need
//let objectOfItem1 = try itemContainer.decode(Type1.self)
// this compiles, but doesn't work because there is no key with these objects
//let objectOfItem1 = try itemContainer.decode(Type1, forKey: .type)
default:
print("test:: it is the default")
}
}
}
private enum CodingKeys: String, CodingKey {
case data
case type
}
And here is the JSON I am trying to decode (many properties committed for clarity):
"contents" : {
"data" : [
{
"type" : "type1",
"id" : "6a406cdd7a9cace5"
},
{
"type" : "type2",
"id" : "ljhdgsouilghoipsu"
}
]
}
How can I correctly get my individual Type1 objects out of this structure?
I think the easy way to get around the heterogenous data is to use an enum as an interim type to wrap your various Item types (which all need to Codable):
To allow myself to test this I've changed your json slightly to give me more heterogenous data for testing. I've used:
let json = """
{
"contents": {
"data": [
{
"type": "type1",
"id": "6a406cdd7a9cace5"
},
{
"type": "type2",
"dbl": 1.01
},
{
"type": "type3",
"int": 5
}
]
}
}
and then created the three final types represented by this json
struct Item1: Codable {
let type: String
let id: String
}
struct Item2: Codable {
let type: String
let dbl: Double
}
struct Item3: Codable {
let type: String
let int: Int
}
To allow decoding the multiple types in a type-safe way (as required by Codable) you need to use a single type that can represent (or wrap) the possible options. An enum with associated values works nicely for this
enum Interim {
case type1 (Item1)
case type2 (Item2)
case type3 (Item3)
case unknown //to handle unexpected json structures
}
So far, so good, but then it gets slightly more complicated when it comes to creating the Interim from the JSON. It will need a CodingKey enum which represents all the possible keys for all the Item# types, and then it will need to decode the JSON linking all these keys to their respective types and data:
extension Interim: Decodable {
private enum InterimKeys: String, CodingKey {
case type
case id
case dbl
case int
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: InterimKeys.self)
let type = try container.decode(String.self, forKey: .type)
switch type {
case "type1":
let id = try container.decode(String.self, forKey: .id)
let item = Item1(type: type, id: id)
self = .type1(item)
case "type2":
let dbl = try container.decode(Double.self, forKey: .dbl)
let item = Item2(type: type, dbl: dbl)
self = .type2(item)
case "type3":
let int = try container.decode(Int.self, forKey: .int)
let item = Item3(type: type, int: int)
self = .type3(item)
default: self = .unknown
}
}
}
This provides the mechanism for decoding the heterogenous components, now we just need to deal with the higher-level keys. As we have a Decodable Interim type this is straightforward:
struct DataArray: Decodable {
var data: [Interim]
}
struct Contents: Decodable {
var contents: DataArray
}
This now means the json can be decoded like this...
let data = Data(json.utf8)
let decoder = JSONDecoder()
do {
let contents = try decoder.decode(Contents.self, from: data)
print(contents)
} catch {
print("Failed to decode JSON")
print(error.localizedDescription)
}
This successfully decodes the data into a nested structure where the major component is the array of Interim types with their associated Item# objects. The above produces the following output, showing these nested types:
Contents(contents: testbed.DataArray(data: [testbed.Interim.type1(testbed.Item1(type: "type1", id: "6a406cdd7a9cace5")), testbed.Interim.type2(testbed.Item2(type: "type2", dbl: 1.01)), testbed.Interim.type3(testbed.Item3(type: "type3", int: 5))]))
I think there should be an even safer way to do this with Type Erasure to provide a more extensible solution, but I've not got my head around that fully yet.
I think you need use this structure:
struct A: Codable {
let contents: B?
enum CodingKeys: String, CodingKey {
case contents
}
struct B: Codable {
let data: [C]?
enum CodingKeys: String, CodingKey {
case data
}
struct C: Codable {
let type : String?
let id : String?
}
}
}
extension A {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let contents = try container.decodeIfPresent(B.self, forKey: .contents)
self.init(contents: contents)
}
}
I'd like to add to flanker's answer an improvement for cleaner approach to avoid having all possible keys to be stored under Interim's CodingKey. Here is an updated Interim
enum Interim: Decodable {
case item1(Item1)
case item2(Item2)
case item3(Item3)
case unknown
init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: Key.self)
// Fallback for any unsupported types
guard let type = try? typeContainer.decode(ItemType.self, forKey: .type) else {
self = .unknown
return
}
// Let corresponding Decodable Item to be initialized from the same decoder.
switch type {
case .type1: self = .item1(try .init(from: decoder))
case .type2: self = .item2(try .init(from: decoder))
case .type3: self = .item3(try .init(from: decoder))
}
}
/// These are values for Item.type
private enum ItemType: String, Decodable {
case type1
case type2
case type3
}
private enum Key: CodingKey {
case type
}
}

How to get the nondecoded attributes from a Decoder container in Swift 4?

I'm using the Decodable protocol in order to parse JSON received from an external source. After decoding the attributes that I do know about there still may be some attributes in the JSON that are unknown and have not yet been decoded. For example, if the external source added a new attribute to the JSON at some future point in time I would like to hold onto these unknown attributes by storing them in a [String: Any] dictionary (or an alternative) so the values do not get ignored.
The issue is that after decoding the attributes that I do know about there isn't any accessors on the container to retrieve the attributes that have not yet been decoded. I'm aware of the decoder.unkeyedContainer() which I could use to iterate over each value however this would not work in my case because in order for that to work you need to know what value type you're iterating over but the value types in the JSON are not always identical.
Here is an example in playground for what I'm trying to achieve:
// Playground
import Foundation
let jsonData = """
{
"name": "Foo",
"age": 21
}
""".data(using: .utf8)!
struct Person: Decodable {
enum CodingKeys: CodingKey {
case name
}
let name: String
let unknownAttributes: [String: Any]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
// I would like to store the `age` attribute in this dictionary
// but it would not be known at the time this code was written.
self.unknownAttributes = [:]
}
}
let decoder = JSONDecoder()
let person = try! decoder.decode(Person.self, from: jsonData)
// The `person.unknownAttributes` dictionary should
// contain the "age" attribute with a value of 21.
I would like for the unknownAttributes dictionary to store the age attribute and value in this case and any other possible value types if they get added to the JSON from the external source in the future.
The reason I am wanting to do something like this is so that I can persist the unknown attributes present in the JSON so that in a future update of the code I will be able to handle them appropriately once the attribute keys are known.
I've done plenty of searching on StackOverflow and Google but haven't yet encountered this unique case. Thanks in advance!
You guys keep coming up with novel ways to stress the Swift 4 coding APIs... ;)
A general solution, supporting all value types, might not be possible. But, for primitive types, you can try this:
Create a simple CodingKey type with string-based keys:
struct UnknownCodingKey: CodingKey {
init?(stringValue: String) { self.stringValue = stringValue }
let stringValue: String
init?(intValue: Int) { return nil }
var intValue: Int? { return nil }
}
Then write a general decoding function using the standard KeyedDecodingContainer keyed by the UnknownCodingKey above:
func decodeUnknownKeys(from decoder: Decoder, with knownKeys: Set<String>) throws -> [String: Any] {
let container = try decoder.container(keyedBy: UnknownCodingKey.self)
var unknownKeyValues = [String: Any]()
for key in container.allKeys {
guard !knownKeys.contains(key.stringValue) else { continue }
func decodeUnknownValue<T: Decodable>(_ type: T.Type) -> Bool {
guard let value = try? container.decode(type, forKey: key) else {
return false
}
unknownKeyValues[key.stringValue] = value
return true
}
if decodeUnknownValue(String.self) { continue }
if decodeUnknownValue(Int.self) { continue }
if decodeUnknownValue(Double.self) { continue }
// ...
}
return unknownKeyValues
}
Finally, use the decodeUnknownKeys function to fill your unknownAttributes dictionary:
struct Person: Decodable {
enum CodingKeys: CodingKey {
case name
}
let name: String
let unknownAttributes: [String: Any]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
let knownKeys = Set(container.allKeys.map { $0.stringValue })
self.unknownAttributes = try decodeUnknownKeys(from: decoder, with: knownKeys)
}
}
A simple test:
let jsonData = """
{
"name": "Foo",
"age": 21,
"token": "ABC",
"rate": 1.234
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let person = try! decoder.decode(Person.self, from: jsonData)
print(person.name)
print(person.unknownAttributes)
prints:
Foo
["age": 21, "token": "ABC", "rate": 1.234]

Resources