How do you parse recursive JSON in Swift? - json

I am receiving JSON from a server where the data is recursive. What is the best way to parse this into a convenient Swift data structure?
Defining a Swift Codable data structure to parse it into fails because the recursive properties are not allowed.
The Swift compiler reports: "Value type 'FamilyTree.Person' cannot have a stored property that recursively contains it"
{
"familyTree": {
"rootPerson": {
"name": "Tom",
"parents": {
"mother": {
"name": "Ma",
"parents": {
"mother": {
"name": "GraMa",
"parents": {}
},
"father": {
"name": "GraPa",
"parents": {}
}
}
},
"father": {
"name": "Pa",
"parents": {}
}
}
}
}
}
Ideally the end result would be a bunch of person objects pointing to their mother and father objects starting from a rootPerson object.

The first thought is to create structs such as:
struct Person: Codable {
var name: String
var parents: Parents
}
struct Parents: Codable {
var mother: Person?
var father: Person?
}
But this doesn't work since you can't have recursive stored properties like this.
Here is one possible working solution:
let json = """
{
"familyTree": {
"rootPerson": {
"name": "Tom",
"parents": {
"mother": {
"name": "Ma",
"parents": {
"mother": {
"name": "GraMa",
"parents": {}
},
"father": {
"name": "GraPa",
"parents": {}
}
}
},
"father": {
"name": "Pa",
"parents": {}
}
}
}
}
}
"""
struct Person: Codable {
var name: String
var parents: [String: Person]
}
struct FamilyTree: Codable {
var rootPerson: Person
}
struct Root: Codable {
var familyTree: FamilyTree
}
let decoder = JSONDecoder()
let tree = try decoder.decode(Root.self, from: json.data(using: .utf8)!)
print(tree)
In a playground this will correctly parse the JSON.
The parents dictionary of Person will have keys such as "mother" and "father". This supports a person have any number of parents with any role.

Possible implementation using classes.
(Swift 5 synthesizes default initializers for classes. Dont remember if so for Swift 4)
import Foundation
var json: String = """
{
"familyTree": {
"rootPerson": {
"name": "Tom",
"parents": {
"mother": {
"name": "Ma",
"parents": {
"mother": {
"name": "GraMa",
"parents": {}
},
"father": {
"name": "GraPa",
"parents": {}
}
}
},
"father": {
"name": "Pa",
"parents": {}
}
}
}
}
}
"""
final class Parents: Codable{
let mother: Person?
let father: Person?
}
final class Person: Codable{
let name: String
let parents: Parents?
}
final class RootPerson: Codable {
var rootPerson: Person
}
final class Root: Codable {
var familyTree: RootPerson
}
var jsonData = json.data(using: .utf8)!
do{
let familyTree = try JSONDecoder().decode(Root.self, from: jsonData)
print("Ma •••>", familyTree.familyTree.rootPerson.parents?.mother?.name)
print("GraPa •••>", familyTree.familyTree.rootPerson.parents?.mother?.parents?.father?.name)
print("Shoud be empty •••>", familyTree.familyTree.rootPerson.parents?.mother?.parents?.father?.parents?.father?.name)
} catch {
print(error)
}

Related

How to parse JSON in Swift with dynamic filename using Codable

I am trying to parse the following JSON into a class, but don't know how to approach this particular case.
Here is the api: https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro=&explaintext=&indexpageids&titles=bird
I am trying to get the title and extract, but in order to do so, it requires that I go through the unique pageid. How would I do this using the Codable protocol?
{
"batchcomplete": "",
"query": {
"normalized": [
{
"from": "bird",
"to": "Bird"
}
],
"pageids": [
"3410"
],
"pages": {
"3410": {
"pageid": 3410,
"ns": 0,
"title": "Bird",
"extract": "..."
}
}
}
}
My suggestion is to write a custom initializer:
Decode pages as [String:Page] dictionary and map the inner dictionaries according to the values in pageids
let jsonString = """
{
"batchcomplete": "",
"query": {
"normalized": [
{
"from": "bird",
"to": "Bird"
}
],
"pageids": [
"3410"
],
"pages": {
"3410": {
"pageid": 3410,
"ns": 0,
"title": "Bird",
"extract": "..."
}
}
}
}
"""
struct Root : Decodable {
let query : Query
}
struct Query : Decodable {
let pageids : [String]
let pages : [Page]
private enum CodingKeys : String, CodingKey { case pageids, pages }
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.pageids = try container.decode([String].self, forKey: .pageids)
let pagesData = try container.decode([String:Page].self, forKey: .pages)
self.pages = self.pageids.compactMap{ pagesData[$0] }
}
}
struct Page : Decodable {
let pageid, ns : Int
let title, extract : String
}
let data = Data(jsonString.utf8)
do {
let result = try JSONDecoder().decode(Root.self, from: data)
print(result)
} catch {
print(error)
}

Parsing nested JSON using Decodable's init(from decoder:)

I'm decoding JSON using Decodable with init(from decoder:) but unable to parse out my nested JSON.
The JSON I'm trying to parse:
The main conflict I believe is parsing the array of edges.
The goal is to get a model I can use with key, value, and namespace.
Would need to use init(from decoder:).
{
"data": {
"collectionByHandle": {
"metafields": {
"edges": [
{
"node": {
"key": "city",
"value": "Fayetteville ",
"namespace": "shipping"
}
},
{
"node": {
"key": "country",
"value": "United States",
"namespace": "shipping"
}
},
{
"node": {
"key": "state",
"value": "AR",
"namespace": "shipping"
}
}
]
}
}
},
"errors": null
}
This technically works but wanting to use init(from decoder: ) and return a model that just has key, value, and namespace without having multiple separate structs/classes.
struct Shipping: Codable {
let data: DataClass
let errors: String?
}
struct DataClass: Codable {
let collectionByHandle: CollectionByHandle
}
struct CollectionByHandle: Codable {
let metafields: Metafields
}
struct Metafields: Codable {
let edges: [Edge]
}
struct Edge: Codable {
let node: Node
}
struct Node: Codable {
let key, value, namespace: String
}

How to create Struct for more then one json table in swift xcode

I am writing an IOS Application that need to read a JSON FIle.
I understood the best way to do that is to write a struct for that json file and parse the json into that struct to be able to use freely.
I have a Json file that is saved locally in one of the folders
{
"colors": [
{
"color": "black",
"category": "hue",
"type": "primary",
"code": {
"rgba": [255,255,255,1],
"hex": "#000"
}
},
{
"color": "white",
"category": "value",
"code": {
"rgba": [0,0,0,1],
"hex": "#FFF"
}
},
{
"color": "red",
"category": "hue",
"type": "primary",
"code": {
"rgba": [255,0,0,1],
"hex": "#FF0"
}
},
{
"color": "blue",
"category": "hue",
"type": "primary",
"code": {
"rgba": [0,0,255,1],
"hex": "#00F"
}
},
{
"color": "yellow",
"category": "hue",
"type": "primary",
"code": {
"rgba": [255,255,0,1],
"hex": "#FF0"
}
},
{
"color": "green",
"category": "hue",
"type": "secondary",
"code": {
"rgba": [0,255,0,1],
"hex": "#0F0"
}
},
],
"people": [
{
"first_name": "john",
"is_valid": true,
"friends_list": {
"friend_names": ["black", "hub", "good"],
"age": 13
}
},
{
"first_name": "michal",
"is_valid": true,
"friends_list": {
"friend_names": ["jessy", "lyn", "good"],
"age": 19
}
},
{
"first_name": "sandy",
"is_valid": false,
"friends_list": {
"friend_names": ["brown", "hub", "good"],
"age": 15
}
},
]
}
i created a struct for each one of the two tables:
import Foundation
struct Color {
var color: String
var category: String
var type: String
var code: [JsonCodeStruct]
}
struct Poeople {
var firsName: String
var is_valid: Bool
var friendsNames: [JsonFriendNames]
}
struct JsonFriendNames {
var friendNames: [String]
var age: String
}
struct JsonCodeStruct {
var rgba: [Double]
var hex: String
}
and I want to open the local json file
and assign it the structs that I gave and then read them easily in the code.
can you suggest me a way on how to do that?
First of all you need an umbrella struct to decode the colors and people keys
struct Root: Decodable {
let colors: [Color]
let people : [Person]
}
The types in your structs are partially wrong. The Color related structs are
struct Color: Decodable {
let color: String
let category: String
let type: String?
let code : ColorCode
}
struct ColorCode: Decodable {
let rgba : [UInt8]
let hex : String
}
and the Person related structs are
struct Person: Decodable {
let firstName : String
let isValid : Bool
let friendsList : Friends
}
struct Friends: Decodable {
let friendNames : [String]
let age : Int
}
Assuming you read the file with
let data = try Data(contentsOf: URL(fileURLWithPath:"/...."))
you can decode the JSON into the given structs with
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let result = try decoder.decode(Root.self, from: data)
print(result)
} catch { print(error) }

Order of JSON node changes while making POST request Swift

I was trying to post the following jSON body
request JSON :
let parameters = [
"createTransactionRequest": [
"merchantAuthentication": [
"name": "xxxxxxxx",
"transactionKey": "xxxxxxxxx"
],
"refId": "123456",
"transactionRequest": [
"transactionType": "authCaptureTransaction",
"amount": "5",
"payment": [
"opaqueData": [
"dataDescriptor": desc!,
"dataValue": tocken!
]
]
]
]
]
When I am trying to print(parameters) the order of node changes it looks like
["createTransactionRequest":
["refId": "123456",
"transactionRequest":
["payment": ["opaqueData": ["dataDescriptor": "COMMON.ACCEPT.INAPP.PAYMENT", "dataValue": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx="]],
"transactionType": "authCaptureTransaction",
"amount": "5"],
"merchantAuthentication": ["name": "xxxxxxx", "transactionKey":
"6gvE46G5seZt563w"]
]
]
I am getting response like
{ messages = {
message = (
{
code = E00003;
text = "The element 'createTransactionRequest' in namespace
'AnetApi/xml/v1/schema/AnetApiSchema.xsd' has invalid child element
'refId' in namespace 'AnetApi/xml/v1/schema/AnetApiSchema.xsd'. List of
possible elements expected: 'merchantAuthentication' in namespace
'AnetApi/xml/v1/schema/AnetApiSchema.xsd'.";
}
);
resultCode = Error;
};
}
This is really annoying. anyones help will be highly grateful.
You need to change your data structure as follow:
struct Transaction: Codable {
let createTransactionRequest: CreateTransactionRequest
}
struct CreateTransactionRequest: Codable {
let merchantAuthentication: MerchantAuthentication
let refId: String
let transactionRequest: TransactionRequest
}
struct MerchantAuthentication: Codable {
let name: String
let transactionKey: String
}
struct TransactionRequest: Codable {
let transactionType: String
let amount: String
let payment: Payment
}
struct Payment: Codable {
let opaqueData: OpaqueData
}
struct OpaqueData: Codable {
let dataDescriptor: String
let dataValue: String
}
Testing
let json = """
{ "createTransactionRequest":
{ "merchantAuthentication":
{ "name": "YOUR_API_LOGIN_ID",
"transactionKey": "YOUR_TRANSACTION_KEY"
},
"refId": "123456",
"transactionRequest":
{ "transactionType": "authCaptureTransaction",
"amount": "5",
"payment":
{ "opaqueData":
{ "dataDescriptor": "COMMON.ACCEPT.INAPP.PAYMENT",
"dataValue": "PAYMENT_NONCE_GOES_HERE"
}
}
}
}
}
"""
let jsonData = Data(json.utf8)
do {
let transaction = try JSONDecoder().decode(Transaction.self, from: jsonData)
print(transaction)
// encoding
let encodedData = try JSONEncoder().encode(transaction)
print(String(data: encodedData, encoding: .utf8)!)
} catch {
print(error)
}
{"createTransactionRequest":{"merchantAuthentication":{"name":"YOUR_API_LOGIN_ID","transactionKey":"YOUR_TRANSACTION_KEY"},"refId":"123456","transactionRequest":{"transactionType":"authCaptureTransaction","amount":"5","payment":{"opaqueData":{"dataValue":"PAYMENT_NONCE_GOES_HERE","dataDescriptor":"COMMON.ACCEPT.INAPP.PAYMENT"}}}}}

Get JSON element swift

I am working receiving the following JSON file in Swift and I cant figure out how get the details elements in the JSON
[
{
"id": 143369,
"history": "jd2",
"details": [
{
"name": "Su 1",
"color": "#ffffff"
},
{
"name": "Stu 1",
"color": "#ffffff"
}
]
},
{
"id": 143369,
"history": "musa 2",
"details": [
{
"name": "Stu 1",
"color": "#ffffff"
},
{
"name": "Stu 2",
"color": "#ffffff"
}
]
}
]
I have created this class with which I am able to retrieve id and history but not the details. How do I include the details with the id and history?
public class students {
let id: Int32
let history: String?
init(id:Int32, history:String) {
self.id = id
self.history = name
}
}
Below is my web service code.
var dataArray = [students]()
Alamofire.request(.GET, url)
.responseJSON { response in
if let value: AnyObject = response.result.value {
let json = JSON(value)
if let items = json.array {
for item in items {
self.dataArray.append(students(
id: item["id"].int32!,
history: item["history"].string!))
let cItems = item["details"].array
for citem in citems {
//here
}
}
}
}
}
your student model should be like this.
let id: Int32
let history: String?
let details: Array<[String:AnyObject]>
init(id:Int32, history:String,details:Array<[String:AnyObject]>) {
self.id = id
self.history = name
self.details= details //need a cast here!
}
here is a simple parser for i used for a project to cast your Array<[String:AnyObject]> as you
func collection(data:[[String:AnyObject]]) -> [yourModel] {
var objectArray: [yourModel] = []
for d in data {
let obj = yourModel(data: d as [String: AnyObject]) // i created a initializer for this object here on my project.
objectArray.append(obj)
}
return objectArray
}
hope gives an idea!