Using Swift 4 Decodable to parse JSON with mixed property values - json

I am trying to update my app to use Swift 4 Decodable - and am pulling down data from a JSON api which has child values which could be:
A Child Object
A String
An Int
Here is the Json Api response:
var jsonoutput =
"""
{
"id": "124549",
"key": "TEST-32",
"fields": {
"lastViewed": "2018-02-17T21:40:38.864+0000",
"timeestimate": 26640
}
}
""".data(using: .utf8)
I have tried to parse it using the following: which works if I just reference the id and key properties which are both strings.
struct SupportDeskResponse: Decodable{
var id: String
var key: String
//var fields: [String: Any] //this is commented out as this approach doesn't work - just generated a decodable protocol error.
}
var myStruct: Any!
do {
myStruct = try JSONDecoder().decode(SupportDeskResponse.self, from: jsonoutput!)
} catch (let error) {
print(error)
}
print(myStruct)
How do I decode the fields object into my Struct?

You should create a new Struct adopting Decodable protocol like this :
struct FieldsResponse: Decodable {
var lastViewed: String
var timeestimate: Int
}
Then you can add it to your SupportDeskResponse
var fields: FieldsResponse

Related

JSON and Realm — do I have to maintain separate structs / classes?

When I receive JSON from an API call, when decoding it I have structs like this:
import Foundation
struct JSON: Codable {
var alpha: Alpha
var beta: Beta
var gamma: [Gamma]?
}
I want to save the JSON in my Realm database, to later use and traverse like JSON. It's my understanding that I can't just use the existing structs I have written, instead I have to rewrite a second (similar) class like this to use with Realm:
import Foundation
import RealmSwift
class RealmJSON: Object, Identifiable {
#Persisted (primaryKey: true) var id: ObjectId
#Persisted var alpha: RealmAlpha
#Persisted var beta: RealmBeta
#Persisted var gamma: RealmSwift.List<RealmGamma>?
override class func primaryKey() -> String? {
"id"
}
convenience init(id: ObjectId, alpha: RealmAlpha, beta: RealmBeta, gamma: RealmSwift.List<RealmGamma>?) {
self.init()
self.id = id
self.alpha = alpha
self.beta = beta
self.gamma = gamma
}
}
Obviously, this is inconvenient especially when dealing with large amounts of JSON. Moreover I want to use Swagger codegen to write the client code for me, but it kind of defeats the purpose if I then have to manually add the Realm classes manually.
Is this the only way for dealing with JSON and a Realm database, or am I missing something here?
EDIT: I realise a simple way is to store most of the JSON as a raw JSON string with properties to identify schema type / version. Then I can just fetch the correct schema I require and parse the rawJSON string with the existing JSON structs...
You can pass the json data directly to your objects. I can think of two ways.
The first way, conform to Codable.
class Dog: Object, Codable {
#Persisted var name: String
}
class Cat: Object, Codable {
#Persisted var name: String
}
class Kid: Object, Codable {
#Persisted var name: String
}
class Owner: Object, Codable {
#Persisted var name: String
#Persisted var dog: Dog?
#Persisted var cat: Cat?
#Persisted var kids: List<Kid>
}
Let's use the following method to make json data:
func makeData() -> Data {
let string = """
{
"name": "Tom",
"kids": [{"name": "Penelope"}, {"name": "Rob"}],
"cat": {"name": "Lilly"},
"dog": {"name": "Lucy"}
}
"""
return string.data(using: .utf8)!
}
Now we can create our objects:
func decodeOwner() {
let decoder = JSONDecoder()
let owner = try! decoder.decode(Owner.self, from: makeData())
print("Decoded:", owner)
}
Another way is to use JSONSerialization and use the result to pass to the value constructor:
extension Object {
convenience init(json: Data) throws {
let data = try JSONSerialization.jsonObject(with: json)
self.init(value: data)
}
}
func serializeOwner() {
let owner = try! Owner(json: makeData())
print("Serialization:", owner)
}

Parse Json to nested Struct using Swift

I am trying to parse a JSON that I am receiving in my Application. The JSON Syntax is correct but I am unable to parse it into a nested Struct.
Here is my code that can be run in Playground:
let message = "{\"type\":\"something\",\"data\":{\"one\":\"first\",\"two\":\"second\",\"three\":\"third\"}}"
let jsonData = message.data(using: .utf8)!
struct Message: Decodable {
let type: String
struct data: Decodable {
var one: String
var two: String
var three: String
}
}
let receivedMessage: Message = try! JSONDecoder().decode(Message.self, from: jsonData)
The printed Result is Message(type: "something") but the data is not parsed.
How can I parse the data correctly to use it afterwards.
The nested struct/dictionary is the value for key data
struct Message: Decodable {
let type: String
let data: Nested
struct Nested: Decodable {
var one: String
var two: String
var three: String
}
}

How to deserialize below JSON response in swift?

I have a json response. I'm bit confused on how to deserialize it. I want to extract the Id and Name from the below json response and store them as key value pairs in a dictionary.
JSON Response
("[{\"attributes\":{\"type\":\"User\",\"url\":\"/services/data/v1.0/objects/User/01234\"},\"Id\":\"01234\",\"Name\":\"User1\",\"RecordTypeId\":\"1\"},{\"attributes\":{\"type\":\"User\",\"url\":\"/services/data/v1.0/objects/User/01235\"},\"Id\":\"01235\",\"Name\":\"User2\",\"RecordTypeId\":\"2\"},{\"attributes\":{\"type\":\"User\",\"url\":\"/services/data/v1.0/objects/User/01236\"},\"Id\":\"01236\",\"Name\":\"User3\",\"RecordTypeId\":\"3\"},{\"attributes\":{\"type\":\"User\",\"url\":\"/services/data/v1.0/objects/User/01237\"},\"Id\":\"01237\",\"Name\":\"User4\",\"RecordTypeId\":\"4\"}]")
I want to extract the id and name values from above json response and store them in a dictionary like below
dict = [01234:"User1", 01235:"User2", 01236:"User3", 01237:"User4"]
The server's response seems like an array, so you could deserialize it to something like [String].
Then you define a structure that corresponds to the json inside that array, something like this:
struct ResponseElement: Codable {
struct attributes : Codable {
let type, url: String
}
let Id, Name, RecordTypeId: String
}
After that you decode it using this: JSONDecoder().decode([ResponseElement].self, ...
Finally you go over every element in your JSON to fill the dictionary, which has the following type [Int:String]
I tested this on Playgrounds, the full code is:
import Foundation
let serverResponse: [String] = [
"[{\"attributes\":{\"type\":\"User\",\"url\":\"/services/data/v1.0/objects/User/01234\"},\"Id\":\"01234\",\"Name\":\"User1\",\"RecordTypeId\":\"1\"},{\"attributes\":{\"type\":\"User\",\"url\":\"/services/data/v1.0/objects/User/01235\"},\"Id\":\"01235\",\"Name\":\"User2\",\"RecordTypeId\":\"2\"},{\"attributes\":{\"type\":\"User\",\"url\":\"/services/data/v1.0/objects/User/01236\"},\"Id\":\"01236\",\"Name\":\"User3\",\"RecordTypeId\":\"3\"},{\"attributes\":{\"type\":\"User\",\"url\":\"/services/data/v1.0/objects/User/01237\"},\"Id\":\"01237\",\"Name\":\"User4\",\"RecordTypeId\":\"4\"}]"
]
struct ResponseElement: Codable {
struct attributes : Codable {
let type, url: String
}
let Id, Name, RecordTypeId: String
}
var dict:[Int:String] = [:]
if let serverResponseAsData = serverResponse[0].data(using: .utf8),
let response = try? JSONDecoder().decode([ResponseElement].self, from: serverResponseAsData) {
response.forEach { element in
if let idAsInt = Int(element.Id) {
dict[idAsInt] = element.Name
}
}
}
print(dict)
And this prints:
[1235: "User2", 1237: "User4", 1236: "User3", 1234: "User1"]

Swift - Using Decodable to decode JSON array of just strings

I have a sample JSON where its just an array of strings and has no keys and would like to use the Decodable protocol to consume the JSON and create a simple model out of it.
The json looks like this:
{ "names": [ "Bob", "Alice", "Sarah"] }
Just a collection of strings in an simple array.
What I'm unsure about is how do I use the new Swift Decodable protocol to read this into a model without a key.
Most of the examples I've seen assume the JSON has a key.
IE:
// code from: Medium article: https://medium.com/#nimjea/json-parsing-in-swift-2498099b78f
struct User: Codable{
var userId: Int
var id: Int
var title: String
var completed: Bool
}
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
let model = try decoder.decode([User].self, from:
dataResponse) //Decode JSON Response Data
print(model)
} catch let parsingError {
print("Error", parsingError)
}
This above example assumes that the json is a key-value; how can I use the decodable protocol to de-code the JSON without keys?
With thanks
The corresponding struct of this JSON is
struct User: Decodable {
let names: [String]
}
and decode
let model = try decoder.decode(User.self, from: dataResponse)
and get the names with
let names = model.names
or traditionally without the overhead of JSONDecoder
let model = try JSONSerialization.jsonObject(with: dataResponse) as? [String:[String]]
For this simple structure of json , i guess it's better not to create any structs and use
let model = try decoder.decode([String:[String]].self, from: dataResponse)
print(model["names"])
the json fiting for your model is
{
"names": [{
"userId": 2,
"id": 23,
"title": "gdgg",
"completed": true
}]
}
struct Root: Codable {
let names: [User]
}
struct User: Codable {
let userId, id: Int
let title: String
let completed: Bool
}

how to Converting JSON into Codable in swift 4.2?

I am using Xcode 10.1 and Swift 4.2. When i try to convert JSON response into Codable class it gives an error that Expected to decode Array<Any> but found a string/data instead.
My Actual JSON response is Like this from API .
{
"d": "[{\"Data\":{\"mcustomer\":[{\"slno\":1000000040.0,\"fstname\":null}]},\"Status\":true}]"
}
My Model is Like this
class MainData: Codable{
var d: [SubData]
}
class SubData : Codable {
var Data : Customer
var Status : Bool?
}
class Customer : Codable {
var mcustomer : [Detail]
}
class Detail : Codable {
var slno : Double?
var fstname : String?
}
And I am Decode this Model using JSONDecoder()
let decoder = JSONDecoder()
let deco = try decoder.decode(MainData.self, from: data)
but, I am unable to Decode this Json into My Model.
Your API is wrong. You array in json shouldn't have quotation marks around it. Otherwise you're declaring that value for key "d" is string
"[...]"
[...]
Suggestions:
Variables and constants should start with small capital letter. Otherwise for example your Data property would cause confusion with Data type. For renaming it while decoding you can use CodingKeys
If you don't need to encode your model, you can just implement Decodable protocol
You can use struct instead of class for your model
The top-level JSON object is a dictionary with the key "d" and a string value, representing another JSON object (sometimes called "nested JSON"). If the server API cannot be changed then the decoding must be done in two steps:
Decode the top-level dictionary.
Decode the JSON object from the string obtained in step one.
Together with Robert's advice about naming, CodingKeys and using structs it would look like this:
struct MainData: Codable {
let d: String
}
struct SubData : Codable {
let data : Customer
let status : Bool
enum CodingKeys: String, CodingKey {
case data = "Data"
case status = "Status"
}
}
struct Customer : Codable {
let mcustomer : [Detail]
}
struct Detail : Codable {
let slno : Double
let fstname : String?
}
do {
let mainData = try JSONDecoder().decode(MainData.self, from: data)
let subData = try JSONDecoder().decode([SubData].self, from: Data(mainData.d.utf8))
print(subData)
} catch {
print(error)
}
For your solution to work, the JSON reponse has to be following format
let json = """
{
"d": [
{
"Data": {
"mcustomer": [
{
"slno": 1000000040,
"fstname": null
}
]
},
"Status": true
}
]
}
"""
But, as you can see, the JSON response you are getting is quite different than you are expecting. Either you need to ask to change the response or you need to change your model.