Parsing local json file on Swift - json

I have a local JSON and try to decode but got "Expected to decode Array but found a dictionary instead" error. The json file and two structs below:
{
"Stanford University": [{
"type": "government",
"name": "Stanford University",
"city": "Santa Clara",
"major": "Computer Engineering"
},
{
"type": "government",
"name": "Stanford University",
"city": "Santa Clara",
"major": "Economics"
}
],
"Berkeley University": [{
"type": "foundation",
"name": "Berkeley University",
"city": "Alameda",
"major": "Communication"
},
{
"type": "foundation",
"name": "Berkeley University",
"city": "Alameda",
"major": "Physics"
}
]
}
two structs:
struct Universite4: Codable {
let name: String?
let major:[Major]?
}
struct Major: Codable {
let type: String?
let name: String?
let major: String? }
And this is code for data load and decode;
public class DataLoader {
#Published var universite4 = [Universite4]()
init() {
load()
}
func load() {
if let unv4json = Bundle.main.url(forResource: "unv4", withExtension: "json") {
do {
let data = try Data(contentsOf: unv4json)
let jsonDecoder = JSONDecoder()
let dataFromJson = try jsonDecoder.decode([Universite4].self, from:data)
self.universite4 = dataFromJson
} catch {
print("Error: \(error)")
}
}
}
}
Does anybody know how can I fix above code? Regards.

Try to change, the issue here is that actually your keys are sort of "Dynamic keys" which I don't recommend but if you have to use them, so try this.
let dataFromJson = try jsonDecoder.decode([Universite4].self, from:data)
to
let dataFromJson = try jsonDecoder.decode([String:[Major]].self, from:data)

Related

Stuck Decoding Multidimensional JSON From URLSession

I have been stuck for a few days trying to decode a multidimensional JSON array from a URLSession call. This is my first project decoding JSON in SwiftUI. My attempts from reading up on methods others suggest do not seem to work.
Here is my JSON response from the server
"success": true,
"message": "Authorized",
"treeData": {
"Right": {
"G1P1": {
"Name": "John Johnson",
"ID": 387,
"SubText": "USA 2002"
},
"G2P1": {
"Name": "Tammy Johnson",
"ID": 388,
"SubText": "USA 2002"
},
"G2P2": {
"Name": "Susan Johnson",
"ID": 389,
"SubText": "USA 1955"
}
},
"Left": {
"G1P1": {
"Name": "Jane Doe",
"ID": 397,
"SubText": "USA 2002"
},
"G2P1": {
"Name": "John Doe",
"ID": 31463,
"SubText": "USA 2002"
},
"G2P2": {
"Name": "Susan Doe",
"ID": 29106,
"SubText": "USA 1958"
}
}
}
}
Here is my decode block of code
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else {
completion(.failure(.noData))
return
}
guard let treeResponse = try? JSONDecoder().decode([String: TreeResponse].self, from: data) else {
completion(.failure(.decodingError))
return
}
dump(treeResponse)
completion(.success("Hooray"))
}.resume()
And then here are my structs, which is the part I can't seem to figure out
struct TreeResponse: Codable {
let success: Bool
let message: String
let treeData: [String:SideData]
}
struct SideData: Codable {
let personKey: [String:PersonInfo]
}
struct PersonInfo: Codable {
let Name: String
let ID: Int
let SubText: String
}
My hope is to be able to access the decoded data as treeResponse.Right.G1P1.Name
Could really use help moving past this
I will post this as an answer even if it cannot be one, but that way I can at least format it properly :-).
First of all you should learn to pose your questions in a manner that makes it as easy as possible for anyone to execute your code. Swift has a particularly helpful way of doing this, you can run a Playground on it. Then you should start whittling down your question to its essence which appears to be the JSON decode. JSONDecoder usually is very helpful in providing you with decent error messages on what it does not like about your JSON, but you have to print them. A suitable Playground would look as follows:
import UIKit
let jsonStr = """
{
"success": true,
"message": "Authorized",
"treeData": {
"Right": {
"G1P1": {
"Name": "John Johnson",
"ID": 387,
"SubText": "USA 2002"
},
"G2P1": {
"Name": "Tammy Johnson",
"ID": 388,
"SubText": "USA 2002"
},
"G2P2": {
"Name": "Susan Johnson",
"ID": 389,
"SubText": "USA 1955"
}
},
"Left": {
"G1P1": {
"Name": "Jane Doe",
"ID": 397,
"SubText": "USA 2002"
},
"G2P1": {
"Name": "John Doe",
"ID": 31463,
"SubText": "USA 2002"
},
"G2P2": {
"Name": "Susan Doe",
"ID": 29106,
"SubText": "USA 1958"
}
}
}
}
"""
struct TreeResponse: Codable {
let success: Bool
let message: String
let treeData: [String:SideData]
}
struct SideData: Codable {
let personKey: [String:PersonInfo]
}
struct PersonInfo: Codable {
let Name: String
let ID: Int
let SubText: String
}
let jsonData = jsonStr.data(using:.utf8)!
do {
let tree = try JSONDecoder().decode(TreeResponse.self, from: jsonData)
print(tree)
} catch {
print(tree)
}
This will yield a somewhat descriptive error message:
keyNotFound(CodingKeys(stringValue: "personKey", intValue: nil),
Swift.DecodingError.Context(codingPath:
[CodingKeys(stringValue: "treeData", intValue: nil),
_JSONKey(stringValue: "Right", intValue: nil)],
debugDescription: "No value associated with key
CodingKeys(stringValue: \"personKey\", intValue: nil)
(\"personKey\").", underlyingError: nil))
(Indentation mine and not particularly well thought out)
This starts pointing out your problems (of which you still seem to have a lot).
The first level of decode is somewhat ok, but the second level is woefully inadequate in its current form. There is no such thing as a personKey in your JSON which would be required to fit it into a simple struct. However you still might be able to coax it through some decode method.
Considering you JSON that appears to be a bad choice and you should opt for properly modelling your tree with the given Left and Right keys, although this is probably scratching the limit of what Decodable will do for you for free, so you will have to put in some more work to get this to work on a more involved example. If the keys on the following levels have any special significance you will have to put in a special decode there too.
In any way, you should definitely learn to formulate your questions better.
when our structure are not perfect to JSON so that's why get this types error and i've use JSONDecoder to retrieve the data from JSON couldn't read the data it's missing, though, such error yet get so needs to create quite perfect JSON models or create model with CodingKeys such like:
struct JSONData: Codable {
let success: Bool
let message: String
let treeData: TreeData
}
struct TreeData: Codable {
let treeDataRight, treeDataLeft: [String: Left]
enum CodingKeys: String, CodingKey {
case treeDataRight = "Right"
case treeDataLeft = "Left"
}
}
struct Left: Codable {
let name: String
let id: Int
let subText: String
enum CodingKeys: String, CodingKey {
case name = "Name"
case id = "ID"
case subText = "SubText"
}
}
For get JSON data to need JSONDecoder():
let jsonData = jsonStr.data(using:.utf8)!
do {
let tree = try JSONDecoder().decode(JSONData.self, from: jsonData)
dump(tree)
} catch {
print(error.localizedDescription)
}
Together with json, JSON model, JSONDecoder():
let jsonStr = """
{
"success": true,
"message": "Authorized",
"treeData": {
"Right": {
"G1P1": {
"Name": "John Johnson",
"ID": 387,
"SubText": "USA 2002"
},
"G2P1": {
"Name": "Tammy Johnson",
"ID": 388,
"SubText": "USA 2002"
},
"G2P2": {
"Name": "Susan Johnson",
"ID": 389,
"SubText": "USA 1955"
}
},
"Left": {
"G1P1": {
"Name": "Jane Doe",
"ID": 397,
"SubText": "USA 2002"
},
"G2P1": {
"Name": "John Doe",
"ID": 31463,
"SubText": "USA 2002"
},
"G2P2": {
"Name": "Susan Doe",
"ID": 29106,
"SubText": "USA 1958"
}
}
}
}
"""
struct JSONData: Codable {
let success: Bool
let message: String
let treeData: TreeData
}
struct TreeData: Codable {
let treeDataRight, treeDataLeft: [String: Left]
enum CodingKeys: String, CodingKey {
case treeDataRight = "Right"
case treeDataLeft = "Left"
}
}
struct Left: Codable {
let name: String
let id: Int
let subText: String
enum CodingKeys: String, CodingKey {
case name = "Name"
case id = "ID"
case subText = "SubText"
}
}
let jsonData = jsonStr.data(using:.utf8)!
do {
let tree = try JSONDecoder().decode(JSONData.self, from: jsonData)
dump(tree)
} catch {
print(error.localizedDescription)
}
Result:
Result here
and i hope this would work and helpfully so try once

How to parse this type of data to a JSON in Swift?

I have called an API to get all holidays in a year, it came out a Json type. But I only can extract it like below (it is one of many elements of "items")
"items": [
{
"kind": "calendar#event",
"etag": "\"3235567993214000\"",
"id": "20200101_1814eggq09ims8ge9pine82pclgn49rj41262u9a00oe83po05002i01",
"status": "confirmed",
"htmlLink": "https://www.google.com/calendar/event?eid=MjAyMDAxMDFfMTgxNGVnZ3EwOWltczhnZTlwaW5lODJwY2xnbjQ5cmo0MTI2MnU5YTAwb2U4M3BvMDUwMDJpMDEgZW4udWsjaG9saWRheUB2",
"created": "2021-04-07T08:26:36.000Z",
"updated": "2021-04-07T08:26:36.607Z",
"summary": "New Year's Day",
"creator": {
"email": "en.uk#holiday#group.v.calendar.google.com",
"displayName": "Holidays in United Kingdom",
"self": true
},
"organizer": {
"email": "en.uk#holiday#group.v.calendar.google.com",
"displayName": "Holidays in United Kingdom",
"self": true
},
"start": {
"date": "2020-01-01"
},
"end": {
"date": "2020-01-02"
},
"transparency": "transparent",
"visibility": "public",
"iCalUID": "20200101_1814eggq09ims8ge9pine82pclgn49rj41262u9a00oe83po05002i01#google.com",
"sequence": 0,
"eventType": "default"
},
{
"kind": "calendar#event",
"etag": "\"3235567993214000\"",
"id": "20200412_1814eggq09ims8gd8lgn6t35e8g56tbechgniag063i0ue048064g0g",
"status": "confirmed",
"htmlLink": "https://www.google.com/calendar/event?eid=MjAyMDA0MTJfMTgxNGVnZ3EwOWltczhnZDhsZ242dDM1ZThnNTZ0YmVjaGduaWFnMDYzaTB1ZTA0ODA2NGcwZyBlbi51ayNob2xpZGF5QHY",
"created": "2021-04-07T08:26:36.000Z",
"updated": "2021-04-07T08:26:36.607Z",
"summary": "Easter Sunday",
"creator": {
"email": "en.uk#holiday#group.v.calendar.google.com",
"displayName": "Holidays in United Kingdom",
"self": true
},
"organizer": {
"email": "en.uk#holiday#group.v.calendar.google.com",
"displayName": "Holidays in United Kingdom",
"self": true
},
"start": {
"date": "2020-04-12"
},
"end": {
"date": "2020-04-13"
},
"transparency": "transparent",
"visibility": "public",
"iCalUID": "20200412_1814eggq09ims8gd8lgn6t35e8g56tbechgniag063i0ue048064g0g#google.com",
"sequence": 0,
"eventType": "default"
}
I try to get the value in key "start" and key "summary" but I can't.
Xcode told me that "items" is a __NSArrayI type.
What I've tried so far is create a class simple like this (just use to try first, so I didn't make all variable)
class API_Info {
var kind: String?
var etag: String?
var id: String?
var status: String?
var htmlLink: String?
var created: String?
var updated: String?
var summary: String?
init(items: [String:Any]){
self.kind = items["kind"] as? String
self.etag = items["etag"] as? String
self.id = items["id"] as? String
self.status = items["status"] as? String
self.htmlLink = items["htmlLink"] as? String
self.created = items["created"] as? String
self.updated = items["updated"] as? String
self.summary = items["summary"] as? String
}
}
And I parse like this:
guard let items = json!["items"]! as? [API_Info] else{
print("null")
return
}
In this way, else statement was run.
What am I doing wrong, and how can I get the data I want?
Thanks in advance.
Codable is the solution here. Below is the struct I used rather than your class
struct ApiInfo: Codable {
let kind: String
let etag: String
let id: String
let status: String
let htmlLink: String
let created: Date
let updated: Date
let summary: String
}
Then I created a root type to hold the array
struct Result: Codable {
let items: [ApiInfo]
}
And then the decoding
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
do {
let result = try decoder.decode(Result.self, from: data)
print(result.items)
} catch {
print(error)
}
Notice that the data values are decoded to Date objects.
Optionally you can skip the root type and decode as a dictionary
do {
let items = try decoder.decode([String: [ApiInfo]].self, from: data)
if let values = items["items"] {
print(values)
}
} catch {
print(error)
}

How to parse local JSON data in Swift?

How to parse local JSON data where nested (optional) property is same as main.
Items data may be available or may not be available.
struct Category: Identifiable, Codable {
let id: Int
let name: String
let image: String
var items: [Category]?
}
I am using common Bundle extension to parse JSON data.
extension Bundle {
func decode<T: Codable>(_ file: String) -> T {
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) from bundle.")
}
let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.dateFormat = "y-MM-dd"
decoder.dateDecodingStrategy = .formatted(formatter)
guard let loaded = try? decoder.decode(T.self, from: data) else {
fatalError("Failed to decode \(file) from bundle.")
}
return loaded
}
}
For eg data :
[
{
"id": 1,
"name": "Apple",
"image": "img_url",
"items" : [
{
"id": 1,
"name": "iPhone",
"image": "img_url",
"items" : [
{
"id": 1,
"name": "iPhone 11 Pro",
"image": "img_url"
},
{
"id": 2,
"name": "iPhone 11 Pro Max",
"image": "img_url"
}
]
},
{
"id": 2,
"name": "iPad",
"image": "img_url",
"items" : [
{
"id": 1,
"name": "iPad mini",
"image": "img_url"
},
{
"id": 2,
"name": "iPad Air",
"image": "img_url"
},
{
"id": 3,
"name": "iPad Pro",
"image": "img_url"
}
]
}
]
},
{
"id": 2,
"name": "Samsung",
"image": "img_url",
"items" : [
{
"id": 1,
"name": "Phone",
"image": "img_url"
},
{
"id": 2,
"name": "Tablet",
"image": "img_url"
}
]
}
]
Nesting is not the issue here, You are facing an Array of Contents. so you should pass [Content] to the decoder like:
let jsonDecoder = JSONDecoder()
try! jsonDecoder.decode([Category].self, from: json)
🎁 Property Wrapper
You can implement a simple property wrapper for loading and decoding all of your properties:
#propertyWrapper struct BundleFile<DataType: Decodable> {
let name: String
let type: String = "json"
let fileManager: FileManager = .default
let bundle: Bundle = .main
let decoder = JSONDecoder()
var wrappedValue: DataType {
guard let path = bundle.path(forResource: name, ofType: type) else { fatalError("Resource not found") }
guard let data = fileManager.contents(atPath: path) else { fatalError("File not loaded") }
return try! decoder.decode(DataType.self, from: data)
}
}
Now you can have any property that should be loaded from a file in a Bundle like:
#BundleFile(name: "MyFile")
var contents: [Content]
Note that since the property should be loaded from the bundle, I raised a FatalError. Because the only person should be responsible for these errors is the developer at the code time (not the run time).

Issue with Codable and JSON

I'm trying to avoid use all the boilerplate code stuff without codable.
I apologize if this a dumb question, but why am I getting this error when I'm trying to parse json with codable?
keyNotFound(CodingKeys(stringValue: "name", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"name\", intValue: nil) (\"name\").", underlyingError: nil))
The json endpoint is structured as follows:
{
"businesses": [
{
"id": "FmGF1B-Rpsjq1f5b56qMwg",
"alias": "molinari-delicatessen-san-francisco",
"name": "Molinari Delicatessen",
"image_url": "https://s3-media3.fl.yelpcdn.com/bphoto/6He-NlZrAv2mDV-yg6jW3g/o.jpg",
"is_closed": false,
"url": "https://www.yelp.com/biz/molinari-delicatessen-san-francisco?adjust_creative=Js10QwuboHe9ZMZF31mwuw&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=Js10QwuboHe9ZMZF31mwuw",
"review_count": 1058,
"categories": [
{
"alias": "delis",
"title": "Delis"
}
],
"rating": 4.5,
"coordinates": {
"latitude": 37.79838,
"longitude": -122.40782
},
"transactions": [
"pickup",
"delivery"
],
"price": "$$",
"location": {
"address1": "373 Columbus Ave",
"address2": "",
"address3": "",
"city": "San Francisco",
"zip_code": "94133",
"country": "US",
"state": "CA",
"display_address": [
"373 Columbus Ave",
"San Francisco, CA 94133"
]
},
"phone": "+14154212337",
"display_phone": "(415) 421-2337",
"distance": 1453.998141679007
}
}
I have a struct I created
struct Businesses: Codable {
var name:String
var image_url:String
var is_closed:Bool
var location:[String:String]
var display_phone:String
var url:String
}
Here's how I'm trying to use Codable:
do{
let jsonData = response.data
//created the json decoder
let decoder = JSONDecoder()
//using the array to put values
self.searchResults = [try decoder.decode(Businesses.self, from: jsonData!)]
It's a very common mistake, you are ignoring the root object which – of course – does not have a key name.
And you'll get another error, location is not [String:String]
struct Root : Decodable {
let businesses : [Business]
}
// Name this kind of struct in singular form
struct Business : Decodable {
let name: String
let imageUrl: URL
let isClosed: Bool
let location: Location
let displayPhone: String
let url: URL
}
struct Location : Decodable {
let address1, address2, address3: String
let city, zipCode, country, state: String
let displayAddress: [String]
}
...
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let jsonData = response.data
let result = try decoder.decode(Root.self, from: jsonData!)
self.searchResults = result.businesses
} 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"}}}}}