Extracting usable data from an API response in Swift - json

I picked up a project from a previous dev that kind of left a mess. We've moved to a new API with a better structure and I'm very confused as to how I can get this to work.
I've got an API that I'm trying to parse the data from so it's in a usable form and I'm new enough that I could use some help.
Could someone take a look at the following code and kind of guide me and explain what's going on here andy maybe how to get it to work properly? I've been beating my head against the wall on this one for a few days now.
Here's a sample of the JSON response that I'm getting:
{
“Green Shirt": [
{
"id": "740",
"name": “Nice Green Shirt",
"quantity": "0",
"make": "",
"model": "",
"price": “15.00",
"size": "XXS",
"sku": null,
"image": "https:\/\/google.com\/green_shirt.jpg",
"new_record": false,
"category_name": "",
"bar_code": "",
},
{
"id": "743",
"name": "Green Shirt",
"quantity": “68",
"make": "",
"model": "",
"price": “20.00",
"size": "XS",
"sku": null,
"image": "https:\/\/google.com\/green_shirt.jpg",
"new_record": false,
"category_name": "",
"bar_code": "",
}
],
“Dark Blue Jeans": [
{
"id": "1588",
"name": "Dark Blue Jeans",
"quantity": "0",
"make": "",
"model": "",
"price": "0.00",
"size": “S",
"sku": null,
"image": "https:\/\/google.com\/dark_blue_jeans.jpg",
"new_record": false,
"category_name": "",
"bar_code": "",
"category": null
},
{
"id": "1559",
"name": "Dark Blue Jeans",
"quantity": "4",
"make": "",
"model": "",
"price": "0.00",
"size": “XL",
"sku": null,
"image": "https:\/\/google.com\/dark_blue_jeans.jpg",
"new_record": false,
"category_name": "",
"bar_code": "",
"category": null
}
],
“White Belt": [
{
"id": "1536",
"name": "White Belt",
"quantity": "37",
"make": "",
"model": "",
"price": "0.00",
"size": "One Size",
"sku": null,
"image": "https:\/\/google.com\/white_belt.jpg",
"new_record": false,
"category_name": "",
"bar_code": "",
"category": null
}
]
}
Class that should take the JSON response
public class Inventory {
public var products = [Product]()
public init(type: Product.Type, data:[[String:AnyObject]]) {
for productData in data {
products.append(type(data: productData))
}
}
}
Product Class
public class Product {
public var data:[String:AnyObject]
required public init(data: [String:AnyObject]) {
self.data = data
}
}
Main Product Class
class MainProduct : Product {
var id:String? {
return data["id"] as? String
}
var name:String {
return data["model"] as! String
}
var sizes:[String:Int] {
if let items = data["quantities"] as? [String:Int] {
return items
} else {
return [String:Int]()
}
}
var upc :String? {
return data["UPC"] as? String
}
var price:Double {
if let price = data["price"] as? Double {
return price
} else if let price = data["price"] as? NSString {
return price.doubleValue
} else {
fatalError("Did not get a price")
}
}
}

You are missing the code needed to load the JSON file and parse it. Also, the JSON you have posted has some illegal characters which will choke the parser. e.g.: { “Green Shirt": [ The first quote is a curly quote. First, you need to clean all of these up.
Then you could save the JSON along with your source files in, say "data.json". If you did that, you could parse the JSON like this:
var error: NSError? = nil
let path = NSBundle.mainBundle().pathForResource("data", ofType: "json")
let jsonData = NSData(contentsOfFile: path!, options: .DataReadingMappedIfSafe, error: &error)
let jsonResult = NSJSONSerialization.JSONObjectWithData(jsonData!, options: NSJSONReadingOptions.AllowFragments, error: &error) as! NSDictionary
Then you could pull the product data from the parsed JSON dictionary into your Product objects.

Related

Web API how to parse unicode json

I'm really confused by API of web service
Some blocks of data has a format like that:
"ja": "犯罪\u8005の子孫た\u3061が暮\u3089\u3059スラム\u8857\u3002\u5883界線の\u5411\u3053\u3046の人\u3005か\u3089は「族民」\u3068\u3055\u3052\u3059まれ\u3001差\u5225\u3092受\u3051\u3066\u3044た\u3002孤\u5150\u3060\u3063た少年・ルドは\u3001育\u3066の親\u3067\u3042るレグト\u3068\u5171にスラム\u8857に住み\u3001常人離れ\u3057た身体能力\u3092武\u5668に生計\u3092立\u3066\u3066\u3044た\u3002\u3060が\u3042る日\u3001身に覚\u3048のな\u3044罪\u3092\u7740せ\u3089れ\u3001スラムの人\u3005\u3067\u3055\u3048\u6050れる「\u5948落」\u3078\u3068落\u3068\u3055れ\u3066\u3057ま\u3046\u2026\u2026\u3002"
I realized that Swift do not work with those format. But I really don't know in what step I should convert this
My parse func:
URLSession
.shared
.dataTask(with: request) { data, response, error in
if let data = data {
do {
if let sanitisedData = String(data: data, encoding: .utf8)!.map({ String($0) }).filter({ $0 != "\\" }).joined(separator: "").replacingOccurrences(of: "U(.*?)", with: "\\\\u$1", options: .regularExpression).data(using: .utf8) {
let decoder = JSONDecoder()
let decodedResponse = try decoder.decode(Welcome.self, from: sanitisedData)
DispatchQueue.main.async {
self.data = decodedResponse
}
}
} catch let jsonError as NSError {
print("JSON Decode Failed: \(jsonError.localizedDescription)")
}
return
}
}
.resume()
Output:
JSON Decode Failed: The data couldn’t be read because it isn’t in the correct format.
I tried to convert this data to string and that's the result:
犯罪u8005の子孫たu3061が暮u3089u3059スラムu8857u3002u5883界線のu5411u3053u3046の人u3005かu3089は「族民」u3068u3055u3052u3059まれu3001差u5225u3092受u3051u3066u3044たu3002孤u5150u3060u3063た少年・ルドはu3001育u3066の親u3067u3042るレグトu3068u5171にスラムu8857に住みu3001常人離れu3057た身体能力u3092武u5668に生計u3092立u3066u3066u3044たu3002u3060がu3042る日u3001身に覚u3048のなu3044罪u3092u7740せu3089れu3001スラムの人u3005u3067u3055u3048u6050れる「u5948落」u3078u3068落u3068u3055れu3066u3057まu3046u2026u2026u3002
Example of response:
{
"result": "ok",
"response": "collection",
"data": [
{
"id": "f2e906bb-8329-4f93-af70-b6344f18aa07",
"type": "manga",
"attributes": {
"title": {
"en": "This players think i\u2019m one of them"
},
"altTitles": [
{
"ru": "Э\u0442\u0438 \u0438\u0433\u0440ок\u0438 \u0434\u0443м\u0430ю\u0442, \u0447\u0442о я о\u0434\u0438н \u0438\u0437 н\u0438\u0445"
}
],
"description": {
"en": "I am the Dark Lord. The one who united the demons. One who wields limitless power and might. The one who has had enough of it all.\n\nThese people have attacked our lands again!\n\nFor a hundred years now.... vile players have attacked our hellish lands, mercilessly plundering and killing peaceful demons.\n\nToday I will become human, infiltrate the ranks of humanity and destroy their rotten system from within... while these players think I'm one of them.",
"ru": "Я - \u0422\u0435мны\u0439 по\u0432\u0435л\u0438\u0442\u0435ль. \u0422о\u0442, к\u0442о о\u0431ъ\u0435\u0434\u0438н\u0438л \u0434\u0435моно\u0432. \u0422о\u0442, к\u0442о о\u0431л\u0430\u0434\u0430\u0435\u0442 \u0431\u0435\u0441кон\u0435\u0447но\u0439 \u0432л\u0430\u0441\u0442ью \u0438 \u0441\u0438ло\u0439. \u0422о\u0442, ком\u0443 \u0432\u0441\u0435 э\u0442о н\u0430\u0434о\u0435ло.\n\nЭ\u0442\u0438 лю\u0434\u0438 \u0432 о\u0447\u0435\u0440\u0435\u0434но\u0439 \u0440\u0430\u0437 н\u0430п\u0430л\u0438 н\u0430 н\u0430\u0448\u0438 \u0437\u0435мл\u0438!\n\n\u0412о\u0442 \u0443\u0436\u0435 \u0441о\u0442ню л\u0435\u0442\u2026. М\u0435\u0440\u0437к\u0438\u0435 \u0438\u0433\u0440ок\u0438 \u0430\u0442\u0430к\u0443ю\u0442 н\u0430\u0448\u0438 \u0430\u0434\u0441к\u0438\u0435 \u0437\u0435мл\u0438, \u0431\u0435\u0441по\u0449\u0430\u0434но \u0433\u0440\u0430\u0431я \u0438 \u0443\u0431\u0438\u0432\u0430я м\u0438\u0440ны\u0445 \u0434\u0435моно\u0432.\n\n\u0412н\u0435\u0441\u0438\u0442\u0435 Э\u0422О!\n\n\u0421\u0435\u0433о\u0434ня я \u0441\u0442\u0430н\u0443 \u0447\u0435ло\u0432\u0435ком, п\u0440о\u0431\u0435\u0440\u0443\u0441ь \u0432 \u0440я\u0434ы \u0447\u0435ло\u0432\u0435\u0447\u0435\u0441\u0442\u0432\u0430 \u0438 \u0443н\u0438\u0447\u0442о\u0436\u0443 \u0438\u0445 п\u0440о\u0433н\u0438\u0432\u0448\u0443ю \u0441\u0438\u0441\u0442\u0435м\u0443 \u0438\u0437н\u0443\u0442\u0440\u0438... пок\u0430 э\u0442\u0438 \u0438\u0433\u0440ок\u0438 \u0434\u0443м\u0430ю\u0442, \u0447\u0442о я о\u0434\u0438н \u0438\u0437 н\u0438\u0445."
},
"isLocked": false,
"links": {
"raw": "https://remanga.org/manga/this-players-think-im-one-of-them?subpath=about"
},
"originalLanguage": "ru",
"lastVolume": "",
"lastChapter": "",
"publicationDemographic": "seinen",
"status": "ongoing",
"year": 2022,
"contentRating": "suggestive",
"tags": [
{
"id": "36fd93ea-e8b8-445e-b836-358f02b3d33d",
"type": "tag",
"attributes": {
"name": {
"en": "Monsters"
},
"description": [],
"group": "theme",
"version": 1
},
"relationships": []
},
{
"id": "391b0423-d847-456f-aff0-8b0cfc03066b",
"type": "tag",
"attributes": {
"name": {
"en": "Action"
},
"description": [],
"group": "genre",
"version": 1
},
"relationships": []
},
{
"id": "39730448-9a5f-48a2-85b0-a70db87b1233",
"type": "tag",
"attributes": {
"name": {
"en": "Demons"
},
"description": [],
"group": "theme",
"version": 1
},
"relationships": []
},
{
"id": "3e2b8dae-350e-4ab8-a8ce-016e844b9f0d",
"type": "tag",
"attributes": {
"name": {
"en": "Long Strip"
},
"description": [],
"group": "format",
"version": 1
},
"relationships": []
},
{
"id": "87cc87cd-a395-47af-b27a-93258283bbc6",
"type": "tag",
"attributes": {
"name": {
"en": "Adventure"
},
"description": [],
"group": "genre",
"version": 1
},
"relationships": []
},
{
"id": "a1f53773-c69a-4ce5-8cab-fffcd90b1565",
"type": "tag",
"attributes": {
"name": {
"en": "Magic"
},
"description": [],
"group": "theme",
"version": 1
},
"relationships": []
},
{
"id": "cdc58593-87dd-415e-bbc0-2ec27bf404cc",
"type": "tag",
"attributes": {
"name": {
"en": "Fantasy"
},
"description": [],
"group": "genre",
"version": 1
},
"relationships": []
},
{
"id": "e197df38-d0e7-43b5-9b09-2842d0c326dd",
"type": "tag",
"attributes": {
"name": {
"en": "Web Comic"
},
"description": [],
"group": "format",
"version": 1
},
"relationships": []
},
{
"id": "f5ba408b-0e7a-484d-8d49-4e9125ac96de",
"type": "tag",
"attributes": {
"name": {
"en": "Full Color"
},
"description": [],
"group": "format",
"version": 1
},
"relationships": []
}
],
"state": "published",
"chapterNumbersResetOnNewVolume": false,
"createdAt": "2022-01-10T17:15:53+00:00",
"updatedAt": "2022-01-10T17:26:26+00:00",
"version": 4,
"availableTranslatedLanguages": [
"ru"
]
},
"relationships": [
{
"id": "5537d8ed-16ed-4f6f-af75-ad7d7edb2ddc",
"type": "author"
},
{
"id": "36ca9e78-35a9-474d-b4e6-0e4065f0af87",
"type": "artist"
},
{
"id": "93e21cc1-1f61-46ce-8362-90d9d6888f63",
"type": "artist"
},
{
"id": "0e703bf4-1996-432c-8620-3c08f2edb37b",
"type": "cover_art"
}
]
}
],
"limit": 1,
"offset": 0,
"total": 57
}
If your server response is really without the unicode escaped, that is,
"犯罪\u8005の子孫た\u3061 ...", then try escaping the data as shown in this example code,
to get: "犯罪\\u8005の子孫た\\u3061 ..." (note the \\) and decode it like this:
URLSession
.shared
.dataTask(with: request) { data, response, error in
if let data = data {
do {
if let sanitisedData = String(data: data, encoding: .utf8)!
.replacingOccurrences(of: "\\", with: "\\\\") // <-- here
.data(using: .utf8) {
let decoder = JSONDecoder()
let decodedResponse = try decoder.decode(Welcome.self, from: sanitisedData)
DispatchQueue.main.async {
self.data = decodedResponse
}
}
} catch let jsonError as NSError {
print("JSON Decode Failed: \(jsonError.localizedDescription)")
}
return
}
}.resume()
EDIT-1:
you may also need to do this: .replacingOccurrences(of: "\n", with: "\\n")
You don't need to process the data returned at all if the result is simply a text (string to say welcome someone). My solution below is just loading a JSON file returned (as you mentioned above) and then decode the data to Welcome object below
struct Welcome: Codable {
let ja: String
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
ja = try values.decodeIfPresent(String.self, forKey: .ja) ?? ""
}
}
func readJsonFile(filename: String) -> String {
guard let fileUrl = Bundle.main.url(forResource: filename, withExtension: "json") else { fatalError() }
guard let jsonData = try? String(contentsOf: fileUrl) else {
return ""
}
return jsonData
}
func parseJsonFile() {
let jsonStr = readJsonFile(filename: "data")
let jsonData: Data = Data(jsonStr.utf8)
let decoder = JSONDecoder()
do {
let welcome = try decoder.decode(Welcome.self, from: jsonData)
print("\(welcome.ja)")
} catch {
print(error.localizedDescription)
}
}
this is the result when I print the welcome.ja's value:
犯罪者の子孫たちが暮らすスラム街。境界線の向こうの人々からは「族民」とさげすまれ、差別を受けていた。孤児だった少年・ルドは、育ての親であるレグトと共にスラム街に住み、常人離れした身体能力を武器に生計を立てていた。だがある日、身に覚えのない罪を着せられ、スラムの人々でさえ恐れる「奈落」へと落とされてしまう……。

Gorm Has Many relationship can't append more than one model instance during association

Summary
Main question: Can I append more than one model instance in a hasMany relationship in gorm or should I use a ManyToMany relationship? E.g. A Product model would have more than one Image model instance.
Here are the relevant models:
type Publication struct {
ID string `gorm:"not null"`
CreatedAt time.Time `sql:"DEFAULT:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `sql:"DEFAULT:CURRENT_TIMESTAMP" json:"updated_at"`
Views uint64 `gorm:"default:0" json:"views"`
Product Product `gorm:"foreignkey:ID" json:"product"`
}
type Product struct {
ID string `gorm:"not null"`
Title string
Price uint64
Images []ProductImage `gorm:"foreignkey:ID" json:"images"`
}
type ProductImage struct {
ID string
Name string
}
During Server Initialization, I create, associate and load some data to the MySQL DB in the following way:
err = db.Debug().Model(&models.Product{}).Create(&mk_list_products[i]).Error
if err != nil {
log.Fatal(err)
}
err = db.Debug().Model(&models.Publication{}).Create(&mk_list_publications[i]).Error
if err != nil {
log.Fatal(err)
}
err = db.Debug().Model(&mk_list_products[i]).Association("Images").Append(&product_images)
if err != nil {
log.Fatal(err)
}
I'm querying Publication model with Product Association as follow:
pbs := []models.Publication{}
err = db.
Debug().
Preload("Product.Images").
Model(&models.Publication{}).
Limit(3).
Where("created_at <= ?", time.Now()).
Find(&pbs).Error
if err != nil {
console.Pretty(err)
return
}
For Publication with ID=3 (similar with the rest) I get
{
"ID": "3",
"created_at": "2022-01-06T21:26:41.585-06:00",
"updated_at": "2022-01-07T21:26:42.154-06:00",
"views": 0,
"product": {
"ID": "3",
"Title": "Product Title A",
"Price": 200,
"category": {
"ID": "",
"Parent": "",
"Image": "",
"Order": 0,
"Name": "",
"Description": "",
"Slug": ""
},
"State": "",
"Description": "Some Description",
"Availability": "",
"Brand": "Some Brand",
"labels": null,
"SKU": "Some SKU",
"images": [
{
"ID": "3",
"Name": "/path/cannabis_product_1.png"
}
]
}
Expected result
{
"ID": "3",
"created_at": "2022-01-06T21:26:41.585-06:00",
"updated_at": "2022-01-07T21:26:42.154-06:00",
"views": 0,
"product": {
"ID": "3",
"Title": "Product Title A",
"Price": 200,
"category": {
"ID": "",
"Parent": "",
"Image": "",
"Order": 0,
"Name": "",
"Description": "",
"Slug": ""
},
"State": "",
"Description": "Some Description",
"Availability": "",
"Brand": "Some Brand",
"labels": null,
"SKU": "Some SKU",
"images": [
{
"ID": "2",
"Name": "/path/cannabis_product_2.png"
}
{
"ID": "3",
"Name": "Name": "/path/cannabis_product_3.png""
}
]
}
Solution
I still don't understand why declaring the ID of ImageProduct as the foreign on the Product model gives unexpected result.
To make this work, I had to defined and add a different foreignKey other than ID on product Imagesfield
type Product struct {
ID string `gorm:"not null"`
Title string
Price uint64
...
Images []ProductImage `gorm:"foreignkey:ProductId" json:"images"`
}
Then, on the ProductImage model I added an additional field ProductId
type ProductImage struct {
ID string
ProductId string
Name string
}
After appending two test ProductImage instances to Publication with ID=3 I get the expected result.
err = db.Debug().Model(&mk_list_products[2]).Association("Images").Append(&product_images[2], &product_images[3])
if err != nil {
log.Fatal(err)
}
...
{
"ID": "3",
"created_at": "2022-01-07T11:20:32.298-06:00",
"updated_at": "2022-01-08T11:20:32.867-06:00",
"views": 0,
"product": {
"ID": "3",
"Title": "Product Title 3",
"Price": 200,
"category": {
"ID": "",
"Parent": "",
"Image": "",
"Order": 0,
"Name": "",
"Description": "",
"Slug": ""
},
"State": "",
"Description": "Some Description",
"Availability": "",
"Brand": "Some Brand",
"labels": null,
"SKU": "Some SKU",
"images": [
{
"ID": "3",
"ProductId": "3",
"Name": "/path/cannabis_product_3.png"
},
{
"ID": "4",
"ProductId": "3",
"Name": "/path/cannabis_product_4.png"
}
]
}
}

Decoding JSON with Codable issue. keyNotFound error message

I have problem with decoding JSON. I am trying to decode my JSON with
let temp = try JSONDecoder().decode([LastTemperatureResponse].self, from: data).
My Codable structs is following:
struct LastTemperatureResponseElement: Codable {
let measurement: Measurement
}
struct Measurement: Codable {
let ts: String
let sensors: [VportSensor]
}
struct VportSensor: TemperatureSensor, Codable {
var lastUpdate: String!
let address, description: String
let status: String
let temperature: Double
}
Well, if I'm trying to decode my JSON, I am getting error message where it's quite clear
keyNotFound(CodingKeys(stringValue: "status", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "measurement", intValue: nil), CodingKeys(stringValue: "sensors", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"status\", intValue: nil) (\"status\").", underlyingError: nil))
but please take a look on my JSON
[
{
"type": "temperatures",
"ts": "2017-11-08T16:43:59.558Z",
"source": "thermo-king",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2017-11-08T16:43:18.000Z",
"sensors": [
{
"address": "t1",
"description": "LFTest1",
"setpoints": [
{
"address": "s1",
"name": "LFSTest1"
}
]
},
{
"address": "t2",
"description": "LFTest2",
"setpoints": [
{
"address": "s2",
"name": "LFSTest2"
}
]
},
{
"address": "t3",
"description": "LFTest3",
"setpoints": [
{
"address": "s3",
"name": "LFSTest3"
}
]
},
{
"address": "t4",
"description": "LFTest4"
},
{
"address": "t5",
"description": "LFTest5"
},
{
"address": "t6",
"description": "LFTest6"
}
],
"sensor": {
"address": "t1",
"name": "LFTest1"
},
"setpoints": [
{
"address": "s1",
"name": "LFSTest1"
}
]
}
},
{
"type": "temperatures",
"ts": "2018-06-07T07:05:38.962Z",
"source": "1-wire",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2018-06-07T07:05:31.000Z",
"sensors": [
{
"address": "2839A5B104000004",
"description": "1-wire #1",
"status": "ok",
"temperature": 24.8
},
{
"address": "28EFBAB104000061",
"description": "1-wire #3",
"status": "ok",
"temperature": 24.5
},
{
"address": "2845F6B504000034",
"description": "1-wire #2",
"status": "ok",
"temperature": 24.5
}
],
"sensor": {
"address": "2839A5B104000004",
"name": "1-wire #1",
"status": "ok"
},
"temperature": 24.8
}
},
{
"type": "temperatures",
"ts": "2018-06-07T07:11:50.030Z",
"source": "vport",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2018-06-07T07:11:47.000Z",
"sensors": [
{
"address": "1036040010",
"description": "Vport 1-wire",
"status": "high",
"temperature": 26
}
],
"sensor": {
"address": "1036040010",
"name": "Vport 1-wire",
"status": "high"
},
"temperature": 26
}
}
]
So I can guess that is giving error because of first portion of data, but should it be omitted and data generated with the rest?
After tracing your issue, I figured that there is couple of issues, First of all:
You are NOT declaring optionals:
based on the attached json, it seems that there are some of the properties that do not always exist, such as:
status => VportSensor.
temperature => Measurement.
temperature => VportSensor.
temperature => setpoints.
you would need make sure to declare any property that may not received as optional.
Also, The implementation of the Codeable structs:
the implemented structs seem to be not typical to the json response structure, make sure to declare your codable structs to be matched with the received json structure.
Note That:
lastUpdate and description are not used in VportSensor.
Based on my answer, there is no need to TemperatureSensor...
Tip:
When it comes to working with dates (such as ts), you should declare it directly as Date instead of String and then set the convenient dateDecodingStrategy. In your case, it should be a custom one, you could find how to do it in this answer.
Implementation:
Based on the above description, there is the full implementation:
struct Main: Codable {
let type: String
let ts: Date
let source: String
let unit: Unit
let measurement: Measurement
}
struct Unit: Codable {
var number: String
}
struct Measurement: Codable {
let ts: String
let sensors: [VportSensor]
let sensor: VportSensor
let temperature: Double?
}
struct LastTemperatureResponseElement: Codable {
let measurement: Measurement
}
struct VportSensor: Codable {
//let lastUpdate: String!
//let description: String
let address: String
let name: String?
let status: String?
let temperature: Double?
let setpoints: [Setpoint]?
}
struct Setpoint: Codable {
let address: String
let name: String
}
// this part from the mentioned answer for creating custom `dateDecodingStrategy`:
enum DateError: String, Error {
case invalidDate
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
let container = try decoder.singleValueContainer()
let dateStr = try container.decode(String.self)
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
if let date = formatter.date(from: dateStr) {
return date
}
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXXX"
if let date = formatter.date(from: dateStr) {
return date
}
throw DateError.invalidDate
})
Output:
let decoder = JSONDecoder()
do {
let temp = try decoder.decode([Main].self, from: json)
// here we go, `temp` is an array of main object of the json
} catch {
print(error)
}
If you are wondering what is json in the
let temp = try decoder.decode([Main].self, from: json)
I just took the attached json response and add it into Data Object:
let json = """
[
{
"type": "temperatures",
"ts": "2017-11-08T16:43:59.558Z",
"source": "thermo-king",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2017-11-08T16:43:18.000Z",
"sensors": [
{
"address": "t1",
"description": "LFTest1",
"setpoints": [
{
"address": "s1",
"name": "LFSTest1"
}
]
},
{
"address": "t2",
"description": "LFTest2",
"setpoints": [
{
"address": "s2",
"name": "LFSTest2"
}
]
},
{
"address": "t3",
"description": "LFTest3",
"setpoints": [
{
"address": "s3",
"name": "LFSTest3"
}
]
},
{
"address": "t4",
"description": "LFTest4"
},
{
"address": "t5",
"description": "LFTest5"
},
{
"address": "t6",
"description": "LFTest6"
}
],
"sensor": {
"address": "t1",
"name": "LFTest1"
},
"setpoints": [
{
"address": "s1",
"name": "LFSTest1"
}
]
}
},
{
"type": "temperatures",
"ts": "2018-06-07T07:05:38.962Z",
"source": "1-wire",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2018-06-07T07:05:31.000Z",
"sensors": [
{
"address": "2839A5B104000004",
"description": "1-wire #1",
"status": "ok",
"temperature": 24.8
},
{
"address": "28EFBAB104000061",
"description": "1-wire #3",
"status": "ok",
"temperature": 24.5
},
{
"address": "2845F6B504000034",
"description": "1-wire #2",
"status": "ok",
"temperature": 24.5
}
],
"sensor": {
"address": "2839A5B104000004",
"name": "1-wire #1",
"status": "ok"
},
"temperature": 24.8
}
},
{
"type": "temperatures",
"ts": "2018-06-07T07:11:50.030Z",
"source": "vport",
"unit": {
"number": "1226000743"
},
"measurement": {
"ts": "2018-06-07T07:11:47.000Z",
"sensors": [
{
"address": "1036040010",
"description": "Vport 1-wire",
"status": "high",
"temperature": 26
}
],
"sensor": {
"address": "1036040010",
"name": "Vport 1-wire",
"status": "high"
},
"temperature": 26
}
}
]
""".data(using: .utf8)!
You can easily skip the keys those are not getting from server response.
Example JSON response is:
{
"isValid": false,
"pendingAttempts": 2
}
In this json response there is missing "id" field & in our code we have declared it. So we can easily skip it by the following code.
//Code example
struct ResponseModel: Codable {
var id: String? //misng in response
var isValid: Bool?
var token: String?
//initializer
init(id: String?, isValid: Bool?, token: String?) {
self.id = id
self.isValid = isValid
self.token = token
}
//definging the coding keys
enum ResponseModelCodingKeys: String, CodingKey {
//The right hand side keys should be same as of json response keys
case id = "id"
case isValid = "isValid"
case token = "token"
}
//decoding initializer
init(from decoder: Decoder) throws {
var id: String?
var isValid: Bool?
var token: String?
let container = try decoder.container(keyedBy: ResponseModelCodingKeys.self) // defining our (keyed) container
do {
//if found then map
id = try container.decode(String.self, forKey: .id)
}
catch {
//not found then just set the default value
/******** This case will be executed **********/
id = ""
}
do {
//if found then map
isValid = try container.decode(Bool.self, forKey: .isValid)
}
catch {
//not found then just set the default value
isValid = false
}
do {
//if found then map
token = try container.decode(String.self, forKey: .token)
}
catch {
//not found then just set the default value
token = ""
}
//Initializing the model
self.init(id: id, isValid: isValid, token: token)
}
}
This technique is useful when we have common response for multiple API's & each API have some missing keys.

Grab an ID from particular array index

I have some JSON which returns the following:
"data": [
{
"created_time": "2010-09-03T16:07:14+0000",
"name": "Profile Pictures",
"id": "125287297520173"
},
{
"created_time": "2010-12-03T00:05:31+0000",
"name": "Mobile Uploads",
"id": "146617845387118"
},
{
"created_time": "2013-07-27T11:34:50+0000",
"name": "Timeline Photos",
"id": "546011742114391"
},
{
"created_time": "2017-01-04T19:02:40+0000",
"name": "Untitled Album",
"id": "1178578645524361"
},
{
"created_time": "2016-09-10T18:26:25+0000",
"name": "Untitled Album",
"id": "1076646985717528"
},
{
"created_time": "2016-07-06T18:27:09+0000",
"name": "OS X Photos",
"id": "1033031426745751"
},
{
"created_time": "2013-06-22T07:32:01+0000",
"name": "iOS Photos",
"id": "530462737002625"
},
{
"created_time": "2012-05-22T19:01:42+0000",
"name": "Cover Photos",
"id": "370987619616805"
},
{
"created_time": "2015-08-27T18:59:56+0000",
"name": "Untitled Album",
"id": "879780692070826"
},
{
"created_time": "2014-12-06T16:13:01+0000",
"name": "DRMC 2005 Batch",
"id": "761469943901902"
},
{
"created_time": "2013-06-16T09:01:17+0000",
"name": "Instagram Photos",
"id": "528368577212041"
},
{
"created_time": "2012-09-09T17:37:55+0000",
"name": "Liverpool Exclusive",
"id": "416230538425846"
},
{
"created_time": "2012-09-10T16:31:52+0000",
"name": "LIVERPOOL FC TOUR",
"id": "416540875061479"
},
{
"created_time": "2010-06-11T19:37:20+0000",
"name": "cars",
"id": "104577376257832"
},
{
"created_time": "2011-03-29T23:50:18+0000",
"name": "Camera+ Photos",
"id": "174268382622064"
}
]
I would like to grab the id of the field "name": "Profile Pictures" . I am using SwiftyJSON to cast the types and so far I managed to do this :
let dictionary = JSON(result)
// print("albums **************\(dictionary)")
if let data = dictionary["data"].array {
print("data of profilePicture ******* \(data)")
let index = data.index{ $0["name"] == "Profile Pictures" }
}
}
I could detech the name of profilePicture as you can see but now I want to grab the id of that json object. Please help.
Use filter to get the dictionary you're interested in…
let dictionary = JSON(result)
// print("albums **************\(dictionary)")
if let data = dictionary["data"].array {
print("data of profilePicture ******* \(data)")
if let dict = data.filter{ $0["name"] == "Profile Pictures" }.first as? [String: String] {
let id = dict["id"]
}
}
let dictionary = JSON(result)
// print("albums ID are **************\(dictionary)")
if let data = dictionary["data"].array {
print("data of profilePicture ******* \(data)")
if let dict = data.first(where: { ($0["name"].string ) == "Profile Pictures" }) {
let id = dict["id"]
print("my desired id : ********* \(id)")
}
}

How can I return an array of object based on matching ids?

I am trying to match the ids of two json files and return the matching objects. These are the 2 json files:
{
"een": {
"id": "100",
"title": "Entertainment and stuff"
},
"twee": {
"id": "107",
"title": "Sport for everyone"
},
"drie": {
"id": "108",
"title": "Eating is good"
}
}
This is the second one:
[
{
"name": "Entertainment",
"id": "100",
"price": 2600,
"gifted": false
},
{
"name": "Sport",
"id": "107",
"price": 2500,
"gifted": false
}
]
As a result of the 2 matching idvalues I should get:
[
{
"name": "Entertainment",
"id": "100",
"price": 2600,
"gifted": false,
"title": "Entertainment and stuff"
},
{
"name": "Sport",
"id": "107",
"price": 2500,
"gifted": false,
"title": "Sport for everyone"
}
]
I was wondering if there was a fancy way using lodash or something else and do this in a nice compact way?
One possible solution would be to use merge to merge two objects that have the id as the key. This can be done using keyBy on the array and also on the values of the first object. Intersection is used find ids that are in both arrays.
let list1 = {
"een": {
"id": "100",
"title": "Entertainment and stuff"
},
"twee": {
"id": "107",
"title": "Sport for everyone"
},
"drie": {
"id": "108",
"title": "Eating is good"
}
}
let list2 = [
{
"name": "Entertainment",
"id": "100",
"price": 2600,
"gifted": false
},
{
"name": "Sport",
"id": "107",
"price": 2500,
"gifted": false
}
]
let o1 = _.keyBy(_.values(list1), 'id');
let o2 = _.keyBy(list2, 'id');
let matchingIds = _.intersection(_.keys(o1), _.keys(o2));
let result = _.chain(o1)
.pick(matchingIds)
.merge(_.pick(o2,matchingIds))
.values()
.value()
Building on #GruffBunny's answer, you want to take his result and filter out those ids that aren't found in o1 and o2.
let o1 = _.keyBy(_.values(list1), 'id'));
let o2 = _.keyBy(list2, 'id');
let idsToPull = _.difference( _.map(o1, 'id'), _.map(o2, 'id')) //["108"]
let merged = _.values(_.merge(o1, o2 ));
let result = _.filter(merged, function(obj){ return _.indexOf(idsToPull, obj.id) === -1 })