Am I converting JSON data to a Swift variable the long way? - json

I am reading json data from a file and trying to move it into a NSDictionary and I am wondering if there is a more concise way to do it. The code shown below works fine. In my program the Astruct-type variable has more than 25 Bstruct-type variables. I have several files similar to the one I show. So it leads to a lot of cumbersome programming.
import UIKit
struct Bstruct {
var label = String()
var inputTypeStr = String()
var list = [String]()
}
struct Astruct {
// in real situation this has a over 25 variables
// limited to 3 for this question
var number = Bstruct()
var customer = Bstruct()
var location = Bstruct()
}
func loadJson(forFilename fileName: String) -> NSDictionary? {
if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
if let data = NSData(contentsOf: url) {
do {
let dictionary = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as? NSDictionary
return dictionary
} catch {
print("Error!! Unable to parse \(fileName).json")
}
}
print("Error!! Unable to load \(fileName).json")
}
return nil
}
func fillAstruct (value: NSDictionary) -> Bstruct {
return Bstruct(label: value["label"] as! String, inputTypeStr: value["inputTypeStr"] as! String, list: value["list"] as! [String])
}
var aVar = Astruct()
let json = loadJson(forFilename: "document")! as NSDictionary
let d = json["aTemplate"]! as! NSDictionary
print("d = \(d)")
// load d into aVar, appears to be complex particularly if the variable has a large number of variables in aVar and the json file
for d1 in d {
let key = String(describing: d1.0)
let value = d1.1 as! NSDictionary
switch key {
case "number":
aVar.number = fillAstruct(value: value)
case "customer":
aVar.customer = fillAstruct(value: value)
case "location":
aVar.location = fillAstruct(value: value)
default: break
}
}
print("\(aVar)")
The JSON file is shown below:
{
"aTemplate": {
"number": {
"label": "Number",
"inputTypeStr": "selection",
"list": [
"A",
"B",
"C"
]
},
"customer": {
"label": "Customer",
"inputTypeStr": "label",
"list": [
""
]
},
"location": {
"label": "location",
"inputTypeStr": "label",
"list": [
""
]
}
}
}

Related

For Loop with json decode data error requires 'People' to conform to 'Sequence'

I am new to swift . I created simple playground and added the file with extension json into playground . I am trying to decode the result and print the ID by using for loop but , I am getting following error ..
For-in loop requires 'People.Type' to conform to 'Sequence'
Here is my json file ..
{
"id": "1",
"options": [{
"id": "11",
"options": [{
"id": "111",
"options": []
}]
},
{
"id": "2",
"options": [{
"id": "21",
"options": []
},
{
"id": "22",
"options": [{
"id": "221",
"options": []
}]
}
]
}
]
}
Here is the code .. I tried ..
struct People: Codable {
let id: String
let options: [People]
}
func loadJson(filename fileName: String) -> People? {
if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let jsonData = try decoder.decode(People.self, from: data)
print("// Printing the ID of the Decode Json")
for jsondata in jsonData {
print("ID: \(jsonData.id)")
}
return jsonData
} catch {
print("error:\(error)")
}
}
return nil
}
loadJson(filename: "people1")
Here is the screenshot of the error ..
Here I got to run exactly what you are asking for, however I think you should probably consider renaming your struct to something like Person as #Vadian suggested.
struct People: Codable {
let id: String
let options: [People]
}
func loadJson(filename fileName: String) -> People? {
if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let jsonData = try decoder.decode(People.self, from: data)
printPeople(people: [jsonData])
return jsonData
} catch {
print("error:\(error)")
}
}
return nil
}
func printPeople(people: [People]) {
for person in people {
print(person.id)
if (!person.options.isEmpty) {
printPeople(people: person.options)
}
}
}
loadJson(filename: "people")
This will print:
1
11
111
2
21
22
221

How to parse array of objects of json with SwiftUI

completely new to Swift here, so I have no idea how to even use print debugging, so thought I would ask here.
I am trying to parse a series of objects, each one of them in this format:
{
"id": 0,
"title": "Example Title",
"targets": [
{
"name": "Example Name"
},
{
"name": "Example Name"
},
{
"name": "Example Name"
},
{
"name": "Example Name"
}
],
"benefits": [
{
"name": "Example Benefit"
},
{
"name": "Example Benefit"
},
{
"name": "Example Benefit"
}
],
"steps": [
{
"name": "Example Step"
},
{
"name": "Example Step"
}
],
"videoURL": "https://someurl.com"
},
So I have a struct defined as such
struct Obj: Codable, Hashable {
var id:Int
var title:String
var targets:[String]
var benefits:[String]
var steps:[String]
var videoURL:String
}
And using
let objs:[Obj] = decode([Obj].self, from: "./Data/Objs.json")
Where decode is this function
func decode<T: Decodable>(_ type:T.Type, from filename:String) -> T {
guard let json = Bundle.main.url(forResource: filename, withExtension: nil) else {
fatalError("Failed to locate \(filename) in app bundle.")
}
guard let jsonData = try? Data(contentsOf: json) else {
fatalError("Failed to load \(filename) from app bundle.")
}
let decoder = JSONDecoder()
guard let result = try? decoder.decode(T.self, from: jsonData) else {
fatalError("Failed to decode \(filename) from app bundle.")
}
return result
}
However when accessing the data in Objs, I get an error in the SwiftUI preview objs.app may have crashed. Check ... for any crash logs in your application which leads me to believe I am using JSONDecoder() incorrectly. Any help is appreciated!
So the problem here is how you are decoding inner content of the targets, steps and benifits.
Targets/Steps/Benifit contains array of object but in the Codable structure of yours, you have given it as a array of strings [String]. You need to correct that and Check.
Create one new structure:
struct Name : Codable {
var name : String
}
Your final object will look like:
struct Obj: Codable, Hashable {
var id:Int
var title:String
var targets:[Name]
var benefits:[Name]
var steps:[Name]
var videoURL:String
}
The key thing you should remember is that at the time of parsing nested object you will have to create another structure with codable conformance.

How to serialize JSON string to multidimensional NSDictionary

"[{\"person\":\"person1\",\"data\":{\"age\":\"10\",\"name\":\"John\"}},
{\"person\":\"person2\",\"data\":{\"age\":\"20\",\"name\":\"Jonathan\"}},
{\"person\":\"person3\",\"data\":{\"age\":\"30\",\"name\":\"Joe\"}}]"
Note that the value "data" is also a dictionary.
I have a JSON string like above and am trying to serialize like:
if let dataFromString = conf.data(using: .utf8, allowLossyConversion: false) {
let json = try JSON(data: dataFromString)
configuration = json.dictionary ?? [:]
}
However configuration is always an empty dictionary.
You need to parse the JSON you've as an array of dictionaries of type [[String: Any]]. The better modern approach is to use Decodable model to decode the JSON.
let string = """
[
{
"person": "person1",
"data": {
"age": "10",
"name": "John"
}
},
{
"person": "person2",
"data": {
"age": "20",
"name": "Jonathan"
}
},
{
"person": "person3",
"data": {
"age": "30",
"name": "Joe"
}
}
]
"""
let data = Data(string.utf8)
struct Person: Decodable {
let person: String
let data: PersonData
}
struct PersonData: Decodable {
let age, name: String
}
do {
let people = try JSONDecoder().decode([Person].self, from: data)
print(people)
} catch { print(error) }
For the JSON String,
let conf = "[{\"person\":\"person1\",\"data\":{\"age\":\"10\",\"name\":\"John\"}},{\"person\":\"person2\",\"data\":{\"age\":\"20\",\"name\":\"Jonathan\"}},{\"person\":\"person3\",\"data\":{\"age\":\"30\",\"name\":\"Joe\"}}]"
use JSONSerialization's jsonObject(with:options:) method to get the expected response.
if let conf = str.data(using: .utf8 ) {
do {
let dict = try JSONSerialization.jsonObject(with: data, options: []) as? [[String:Any]]
print(dict)
} catch {
print(error)
}
}

Swift: Accessing the 1220 face vertices and save the vertices from AR face

I am trying to get the 1220 vertices data and save them to the iPhone file. The problem I am having is I could not get the captured data and write them into the JSON structure correctly.
I have:
struct CaptureData {
var vertices: [SIMD3<Float>]
var verticesFormatted : String {
let v = "<" + vertices.map{ "\($0.x):\($0.y):\($0.z)" }.joined(separator: "~") + "~t:\(String(Double(Date().timeIntervalSince1970)))>"
return "\(v)"
}
var jsonDict:Dictionary<String, Any> = [
"facetracking_data" : "1",
"info" : [
"frame" : "0",
"timestamp" : 12345,
"vertices" : [
"x" : "0.1",
"y" : "0.2",
"z" : "0.3"
]
]
]
var jsonData : Data {
let data = try! JSONSerialization.data(withJSONObject: jsonDict, options: .prettyPrinted)
return data
}
}
I was able to print some data using print(data), like following:
SIMD3(0.018364782, -0.043627884, 0.0480636), SIMD3(-0.01794959, -0.04421088, 0.04891427), SIMD3(-0.019180747, -0.045720913, 0.049276996)], jsonDict: ["facetracking_data": "1", "info": ["frame": "0", "timestamp": 1590426140.3919969, "vertices": ["x": "0.1", "y": "0.2", "z": "0.3"]]])
I am trying to write the captured data and save them into the JSON format by clicking one button:
#IBAction func saveButtonTapped(_ sender: UIButton) {
guard let data = getFrameData() else {return}
let jsonDataString = String(decoding: data.jsonData, as: UTF8.self)
let file = "\(UUID().uuidString).txt"
let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = dir.appendingPathComponent(file)
do {
try data.write(to: fileURL, atomically: false, encoding: .utf8)
print(data)
}
catch {
print("Error: \(error)")
}
}
I could not get try data.write(to: fileURL, atomically: false, encoding: .utf8) this to work as it return error ('data' have no member of 'write') and I am not too sure how to get the captured data and write them into my defined JSON structure...

read JSON result as array of dictionary

how to parse following JSON result
{
"AddedByName": "jhon",
"ApproveAction": 0,
"ApproveActionName": "",
"photos": null,
"Status": 0,
},
{
"AddedByName": "mike",
"ApproveAction": 0,
"ApproveActionName": "",
"photos": null,
"Status": 0,
},
{
"AddedByName": "someone",
"ApproveAction": 0,
"ApproveActionName": "",
"photos": [
{
"Id": 53,
"Serial": 1,
"Url": "0afe88a3-76e1-4bac-a392-173040936300.jpg"
}
],
"Status": 0,
}
how can i reach the "photos" array ?
I already declare local array of dictionary to hold the whole responses as following
var myLocalArray = [[String:Any]]()
and fill it from the JSON response like this
if let Json = response.result.value as? [String:Any] {
if let ActionData = Json["ActionData"] as? [[String:Any]] {
self. myLocalArray = ActionData
}
}
and it works
but i couldn't reach the "photos" array please help
I will give you smart solution than can be useful at each you try to transform you Json to useful data model and make it easier to manipulate .
Using the power of Decodable .
This Model will help you to capture you Json
struct User: Decodable {
var AddedByName: String
var ApproveAction: Int
var ApproveActionName: String
var photos: [Photo]?
var Status: Int
struct Photo: Decodable {
var Id: Int
var Serial: Int
var Url: String
}
}
And now will be just one line to get your Json to structure data:
let responseData = try JSONDecoder().decode([User].self, from: jsonD)
#swiftIos provided the answer with Decodable which is absolutely a better way to handle the situation.
But with your current you can access the photos from self.myLocalArray:
if let jsonData = response.result.value as? [String: Any] {
if let actionData = jsonData["ActionData"] as? [[String:Any]] {
self.myLocalArray = actionData
}
}
Now you have array for actionData, so access the photos by extracting the actionData for particular index as self.myLocalArray[0]. In whole:
let index = 0
if self.myLocalArray.count > index {
if let photoArrayIndex0 = self.myLocalArray[index]["photos"] as? [[String: Any]] {
print(photoArrayIndex0)
}
}