Parsing JSON in Swift without array key - json

I have a JSON-response:
[
[{
"id": "1",
"name": "Toyota",
"model": "Camry"
},
{
"id": "2",
"name": "Nissan",
"model": "Almera"
}
],
{
"count": "1234",
"page": "1"
}
]
I create decodable model:
struct Car: Decodable {
var id: String?
var name: String?
var model: String?
}
I'm trying extract data like this, for test:
let carsResponse = try JSONDecoder().decode([[Car]].self, from: data)
print(carsResponse[0])
And I have an error:
Expected to decode Array but found a dictionary instead.
What is wrong?

This format is pretty bad, so you'll need to decode the outer container by hand, but it's not difficult:
struct CarResponse: Decodable {
var cars: [Car]
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
cars = try container.decode([Car].self) // Decode just first element
}
}
let carsResponse = try JSONDecoder().decode(CarResponse.self, from: data)
print(carsResponse.cars)

Related

Complex JSON to Swift

So I am relatively new to JSON and converting it to Swift. I have only dealt with basic JSON with simple data types. Nothing fancy. I also have only dealt with using JSON that I have created.
Now I am trying to use JSON that I have gathered from and API. This JSON has nested objects inside of other objects.
here is the format of the JSON:
{
"records": [
{
"id": "Info",
"fields": {
"Card Name": "Info",
"Qty": 0,
"Card Type": "Info",
"Color": "Info",
"Card #": "Info",
"Rarity": "Info",
"Image": [
{
"id": "Info",
"url": "url",
"filename": "info",
"size": 0,
"type": "info",
"thumbnails": {
"small": {
"url": "url",
"width": 0,
"height": 0
},
"large": {
"url": "url",
"width": 0,
"height": 0
},
"full": {
"url": "url",
"width": 0,
"height": 0
}
}
}
],
"Level": 0,
"Full Set": 0
},
"createdTime": "info"
}
],
"offset": "info"
}
I am not sure if I need to do anything with the "records": portion. Now there are more records within the "records": array.
I don't need all of the information within each record.
I tried doing something like:
struct Card {
let offSet: String
let cardName: String
}
extension Card: Decodable {
enum CodingKeys: String, CodingKey {
case offSet
case cardName
enum FieldKeys: String, CodingKey {
case cardName = "Card Name"
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
offSet = try container.decode(String.self, forKey: .offSet)
let fieldContainer = try container.nestedContainer(keyedBy: CodingKeys.FieldKeys.self, forKey: .cardName)
cardName = try fieldContainer.decode(String.self, forKey: .cardName)
}
}
Every time I try to decode the info into my Swift Struct it just comes up nil. Not sure what I am doing wrong or where to go from here. I haven't dealt with JSON that have "records": setup like this. Not sure if this even matters.
Also a side question in regards to images. Would I just use the url property to display the image? The image files will be PNG. Never dealt with images and JSON before.
As Larme commented "Your code can't guess" - Meaning you need to represent the same structure in code as your JSON provides.
In danger of doing the work for you I've written a very verbose set of Codable struct's that pull out all of the data from the JSON you provided.
struct Card: Codable {
var records:[Record]
var offset:String
}
struct Record: Codable {
var id:String
var fields:Fields
var createdTime:String
}
struct Fields: Codable {
var name:String
var quantity:Int
var type:String
var color:String
var number:String
var rarity:String
var level:Int
var fullSet:Int
var image:[Image]
enum CodingKeys: String, CodingKey {
case name = "Card Name"
case quantity = "Qty"
case type = "Card Type"
case color = "Color"
case number = "Card #"
case rarity = "Rarity"
case level = "Level"
case fullSet = "Full Set"
case image = "Image"
}
}
struct Image: Codable {
var id:String
var url:String
var filename:String
var size:Int
var type:String
var thumbnails:Thumbnails
}
struct Thumbnails: Codable {
var small:Thumbnail
var large:Thumbnail
var full:Thumbnail
}
struct Thumbnail: Codable {
var url:String
var width:Int
var height:Int
}
I tested the above in a playground by pasting the JSON you provided as a multiline string and using the following to dump the resulting objects.
do {
if let data = json.data(using: .utf8) {
let card = try JSONDecoder().decode(Card.self, from: data)
dump(card)
}
} catch {
fatalError("Failed to decode \(error)")
}
The output of which was:
▿ __lldb_expr_21.Card
▿ records: 1 element
▿ __lldb_expr_21.Record
- id: "Info"
▿ fields: __lldb_expr_21.Fields
- name: "Info"
- quantity: 0
- type: "Info"
- color: "Info"
- number: "Info"
- rarity: "Info"
- level: 0
- fullSet: 0
▿ image: 1 element
▿ __lldb_expr_21.Image
- id: "Info"
- url: "url"
- filename: "info"
- size: 0
- type: "info"
▿ thumbnails: __lldb_expr_21.Thumbnails
▿ small: __lldb_expr_21.Thumbnail
- url: "url"
- width: 0
- height: 0
▿ large: __lldb_expr_21.Thumbnail
- url: "url"
- width: 0
- height: 0
▿ full: __lldb_expr_21.Thumbnail
- url: "url"
- width: 0
- height: 0
- createdTime: "info"
- offset: "info"
Once you have all the data you could start simplifying things, perhaps an extension to the Card that gets the first records name.
extension Card {
var cardName:String {
guard let firstRecord = self.records.first else {
return "Unknown Name"
}
return firstRecord.fields.name
}
}
print(card.cardName) // "Info\n"

How to parse a JSON starting with a nameless array?

I'm very new to parsing (and coding)
This is my JSON:
[
{
"id": "BTC",
"currency": "BTC",
"symbol": "BTC",
"name": "Bitcoin",
"logo_url": "https://s3.us-east-2.amazonaws.com/nomics-api/static/images/currencies/btc.svg",
"rank": "1",
"price": "8890.83451549",
"price_date": "2020-05-05T00:00:00Z",
"price_timestamp": "2020-05-05T17:07:00Z",
"market_cap": "163265390419"
}
]
I'm trying to get a hold on the property price with the following:
struct GetPriceArray: Codable {
let getPriceArray: [GetPrice]
}
struct GetPrice: Codable {
let price: String
}
The problem is that the JSON path is 0.price (as indicated by JSON Viewer Awesome) and with the above, it is not possible to get a hold of that 0.
I already tried jumping directly to .price but it does not work.
Delete
struct GetPriceArray: Codable {
let getPriceArray: [GetPrice]
}
and decode an array
let result = JSONDecoder().decode([GetPrice].self, from: ...
Get the price with
result.first?.price
If you want GetPriceArray working with decoder and non-key JSON.
struct GetPriceArray: Codable {
let list: [GetPrice]
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var elements: [GetPrice] = []
elements.reserveCapacity(container.count ?? 0)
while !container.isAtEnd {
if let element = try? container.decode(GetPrice.self) {
elements.append(element)
}
}
self.list = elements
}
}
Test.
guard let jsonData = jsonString.data(using: .utf8),
let getPriceArray = try? JSONDecoder().decode(GetPriceArray.self, from: jsonData) else {
return
}
for i in getPriceArray.list {
print(i.price)
}

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)
}

How to create a Swift model for JSON

{"dataList":{"1547795650562": {
"c0a8007b-6759-111d-8167-59e8dabe0086": {
"recordDate": 1547795650562,
"resultValue": "160",
"vitalParameter": {
"uom": {
"code": "KG",
"name": "KG",
"id": "c0a8007b-6759-111d-8167-59e76204007f"
},
"resultType": {
"code": "VSRTNUMERIC",
"name": "Numeric",
"id": "20cf4756-40b0-4cc1-acb5-861765370a41"
},
"code": "29463-7",
"name": "Weight",
"id": "c0a8007b-6759-111d-8167-59e8dabe0086"
},
"id": "c0a8007b-6855-1d16-8168-5fd18fa301b7"
}}
}}
getting 1547795650562 and c0a8007b-6759-111d-8167-59e8dabe0086 as class names. But I dont want like this;
class DataList : NSObject, NSCoding{
var 1547795650562 : 1547795650562!
}
class 1547795650562 : NSObject, NSCoding{
var c0a8007b6759111d816759e8dabe0086 : VitalParameter!
}
But the problem here is, 1547795650562 and c0a8007b-6759-111d-8167-59e8dabe0086 cannot be hard coded because they may change.
c0a8007b-6759-111d-8167-59e8dabe0086 is dynamic id and 1547795650562 is recordDate. Inner object is repetitive.
But I have to map as the keys are of recordDate and id respectively.
Try using Codable instead of NSCoding to parse your JSON data.
Models:
struct Root: Codable {
let dataList: [String:[String:Record]]
}
struct Record: Codable {
let recordDate: Int
let resultValue: String
let vitalParameter: VitalParameter
let id: String
}
struct VitalParameter: Codable {
let uom, resultType: ResultType
let code, name, id: String
}
struct ResultType: Codable {
let code, name, id: String
}
Parse the JSON data using above models like,
do {
let response = try JSONDecoder().decode(Root.self, from: data)
print(response)
} catch {
print(error)
}
Note: You can use https://app.quicktype.io to get the models from your JSON instantly. Make the changes as per your requirement and you're good to go.

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"}}}}}