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
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
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.
"[{\"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)
}
}
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...
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)
}
}