I have a very long Json array that is full of items that look like this:
[
{
"id": "sm10-1",
"name": "Pheromosa & Buzzwole-GX",
"imageUrl": "https://images.pokemontcg.io/sm10/1.png",
"subtype": "TAG TEAM",
"supertype": "Pokémon",
"hp": "260",
"retreatCost": [
"Colorless",
"Colorless"
],
"convertedRetreatCost": 2,
"number": "1",
"artist": "Mitsuhiro Arita",
"rarity": "Rare Holo GX",
"series": "Sun & Moon",
"set": "Unbroken Bonds",
"setCode": "sm10",
"text": [
"When your TAG TEAM is knocked out, your opponent takes 3 Prize Cards."
],
"types": [
"Grass"
],
"attacks": [
{
"name": "Jet Punch",
"cost": [
"Grass"
],
"convertedEnergyCost": 1,
"damage": "30",
"text": "This attack does 30 damage to 1 of your opponent's Benched Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)"
},
{
"name": "Elegant Sole",
"cost": [
"Grass",
"Grass",
"Colorless"
],
"convertedEnergyCost": 3,
"damage": "190",
"text": "During your next turn, this Pokémon's Elegant Sole attack's base damage is 60."
},
{
"name": "Beast Game-GX",
"cost": [
"Grass"
],
"convertedEnergyCost": 1,
"damage": "50",
"text": "If your opponent's Pokémon is Knocked Out by damage from this attack, take 1 more Prize card. If this Pokémon has at least 7 extra Energy attached to it (in addition to this attack's cost), take 3 more Prize cards instead. (You can't use more than 1 GX attack in a game.)"
}
],
"weaknesses": [
{
"type": "Fire",
"value": "×2"
}
],
"imageUrlHiRes": "https://images.pokemontcg.io/sm10/1_hires.png",
"nationalPokedexNumber": 794
}
]
That is just one item of hundreds in the array. What I want to do is grab specific values from each item (i.e. name, imageUrl, supertype, hp, rarity, set) and send them to a struct which will then be added to an array of such structs.
What I currently have prints just prints out all of the json data and I can not figure out how to get individual data and create an array of structs for each individual card.
Here is the code I have currently:
//[TEST] READING JSON FILE LOCALLY
struct card: Decodable {
let name: String
let imageUrl: String
let supertype: String
let artist: String
let rarity: String
let set: String
let types: Array<String>
}
func loadJsonInfo() {
do{
let data = try Data.init(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "Unbroken Bonds", ofType: "json")!))
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
print(json)
} catch {
print(error)
}
}
Also, the json file is locally stored in my appData. Thanks in advance for your help!
Give a try to https://quicktype.io/
You put json there. And get all necessary data structures to decode json
To decode a JSON with Decodable type, you need to use JSONDecoder's decode(_:from:) method.
Update your loadJsonInfo() method to,
func loadJsonInfo() {
if let file = Bundle.main.url(forResource: "Unbroken Bonds", withExtension: "json") {
do {
let data = try Data(contentsOf: file)
let arr = try JSONDecoder().decode([Card].self, from: data)
print(arr)
} catch {
print(error)
}
}
}
Note: Use first letter capital while creating a type, i.e. use Card instead of card.
Parsing Code when you have DATA from server. I also removed force unwrapped ! so it won't crash in absence of file
func loadJsonInfo() {
if let path = Bundle.main.path(forResource: "Unbroken Bonds", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped)
let result = try JSONDecoder().decode(ResultElement.self, from: data)
} catch let error {
print(error)
}
}
}
Your Model
import Foundation
// MARK: - ResultElement
struct ResultElement: Codable {
let id, name: String?
let imageURL: String?
let subtype, supertype, hp: String?
let retreatCost: [String]?
let convertedRetreatCost: Int?
let number, artist, rarity, series: String?
let resultSet, setCode: String?
let text, types: [String]?
let attacks: [Attack]?
let weaknesses: [Weakness]?
let imageURLHiRes: String?
let nationalPokedexNumber: Int?
enum CodingKeys: String, CodingKey {
case id, name
case imageURL = "imageUrl"
case subtype, supertype, hp, retreatCost, convertedRetreatCost, number, artist, rarity, series
case resultSet = "set"
case setCode, text, types, attacks, weaknesses
case imageURLHiRes = "imageUrlHiRes"
case nationalPokedexNumber
}
}
// MARK: - Attack
struct Attack: Codable {
let name: String?
let cost: [String]?
let convertedEnergyCost: Int?
let damage, text: String?
}
// MARK: - Weakness
struct Weakness: Codable {
let type, value: String?
}
typealias Result = [ResultElement]
Related
I am pretty new to Swift and as an exercise I try to create an App with Swift 5, which should show you the Weather at a Location you searched for.
Right now I am trying to implement a function that can turn a local JSON file in a struct I am using.
The JSON file is located at the same directory as all the other files.
The JSON for testing purposes looks like this:
[
{
"id": 833,
"name": "Ḩeşār-e Sefīd",
"state": "",
"country": "IR",
"coord": {
"lon": 47.159401,
"lat": 34.330502
}
},
{
"id": 2960,
"name": "‘Ayn Ḩalāqīm",
"state": "",
"country": "SY",
"coord": {
"lon": 36.321911,
"lat": 34.940079
}
}
]
The struct:
struct Root: Codable {
let jsonWeather: [JsonWeather]?
}
struct JsonWeather: Codable {
let id: Int
let name: String
let state: String
let country: String
let coord: Coord
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case state = "state"
case country = "country"
case coord = "coord"
}
}
The function that i am working on:
func loadJson(fileName: String) -> Void{
if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
do {
let data = try Data(contentsOf: url)
let jsonData = try JSONDecoder().decode(Array<Root>.self, from: data)
print(type(of: jsonData)) // Array<Root>
print(jsonData) // [WeatherApp.Root(jsonWeather: nil), WeatherApp.Root(jsonWeather: nil)]
} catch {
print("error:\(error)")
}
}
}
After all I wanted to see how the result looks like and I noticed that each jsonWeather in Root is nil
So in the end I understand that the values are nil because I allow them to be nil by setting an optional in the Root struct, but I dont quite understand why they are turning nil because there is data given. Also I don't know how I would implement it without being an optional.
This is my first time using JSONDecoder and Optionals in Swift.
Could anyone point out what I did wrong (or understood wrong) and how to fix this issue
Since your JSON is just of array of objects, you don't need a top-level struct like Root.
Get rid of your Root struct, and just decode an array of JsonWeathers:
let jsonData = try JSONDecoder().decode([JsonWeather].self, from: data)
(Note that there is no difference between [JsonWeather].self and Array<JsonWeather>.self - I just chose to do it this way since it's shorter.)
Let's suppose this json which represents multilingual words:
[{
"id": "en_cat",
"name": "cat",
"def": "A cat is a domestic animal of the feline family.",
"trans": {
"fr": "fr_chat",
"ru": "ru_ко́шка"
}
}, {
"id": "fr_chat",
"name": "chat",
"def": "Le chat est un animal domestique de la famille des félins.",
"trans": {
"en": "en_cat",
"ru": "ru_ко́шка"
}
}, {
"id": "ru_ко́шка",
"name": "ко́шка",
"def": "..."
"trans": {
"en": "en_cat",
"fr": "fr_chat"
}
}]
This json has items related to each others in the "trans" (translation) nested container.
My class is straight forward
class Word: Decodable {
var id: String
var name: String
var definition: String
var enTranslation: Word?
var frTranslation: Word?
var ruTranslation: Word?
enum JsonCodingKey: String, CodingKey {
case id
case name
case def
case trans
}
enum JsonTransCodingKey: String, CodingKey {
case en
case fr
case ru
}
convenience init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: JsonCodingKey.self)
let id = try container.decode(String.self, forKey: .id)
let name = try container.decode(String.self, forKey: .name)
let definition = try container.decode(String.self, forKey: .def)
self.init(id: id, name: name, definition: definition)
// Tricky part here...
let transContainer = try container.nestedContainer(keyedBy: JsonTransCodingKey.self, forKey: .trans)
if let en = transContainer.decode(String.self, forKey: .en) {
self.enTranslation = realm.find(wordId: en) // Singleton that looks into memory for the word
}
// And repeat the same if logic for the other languages...
}
}
What is the fastest (CPU) way to JSON Decode it?
My way of handling it "feels" wrong:
I decode the words using
let jsonDecoder = JSONDecoder()
let words = jsonDecoder.decode([Word].self, from: data)
But the words don't have any translation linked because they are not "known" during the real-time parsing.
In my example, when we parse the first word "cat", we still don't know the French nor Russian words yet.
So I have to decode again, once I have all the words in memory.
let jsonDecoder = JSONDecoder()
let words = jsonDecoder.decode([Word].self, from: data) // Words don't have their translations
self.saveInMemory(words) // In my case, it is saved to Realm.
let words = jsonDecoder.decode([Word].self, from: data)
/* Words are now linked to each others
Because during decoding, the func Word.init(from decoder) will
look into `Realm` and find the translations. */
This double decoding feels like an overkill. Isn't there anyway to search into the json data directly?
Decode first, generate your structure later. You are trying to combine the two which does not make sense.
Your first decoding does the actual decoding, your second decoding does only the linking.
Instead of that, decode to temporary structures, build a dictionary of identifiers and use that to link them to final objects.
To be honest, there is no need to do the actual linking. It could still be completely dynamic, using a dictionary.
One possible approach:
let data = """
[{
"id": "en_cat",
"name": "cat",
"def": "A cat is a domestic animal of the feline family.",
"trans": {
"fr": "fr_chat",
"ru": "ru_ко́шка"
}
}, {
"id": "fr_chat",
"name": "chat",
"def": "Le chat est un animal domestique de la famille des félins.",
"trans": {
"en": "en_cat",
"ru": "ru_ко́шка"
}
}, {
"id": "ru_ко́шка",
"name": "ко́шка",
"def": "...",
"trans": {
"en": "en_cat",
"fr": "fr_chat"
}
}]
""".data(using: .utf8)!
enum Language: String {
case english = "en"
case french = "fr"
case russian = "ru"
}
class Word: Decodable {
let id: String
let name: String
let definition: String
let translationsIds: [String: String]
weak var parentDictionary: Dictionary!
private enum CodingKeys: String, CodingKey {
case id
case name
case definition = "def"
case translationsIds = "trans"
}
func translation(for language: Language) -> Word? {
return translationsIds[language.rawValue].flatMap { parentDictionary.words[$0] }
}
}
class Dictionary: Decodable {
let words: [String: Word]
required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let words = try container.decode([Word].self)
self.words = [String: Word](uniqueKeysWithValues: words.map { (key: $0.id, value: $0) })
for word in words {
word.parentDictionary = self
}
}
}
let decoder = JSONDecoder()
let dictionary = try decoder.decode(Dictionary.self, from: data)
print(dictionary.words["fr_chat"]?.translation(for: .english)?.name)
I'm using JSONDecoder to decode from a JSON file which has nested dictionaries. It fails to decode from the json data to my customized model.
This is what I have tried in my code.
The JSONDecoder looks like this:
let netWorkManager = NetWorkManager(URL: url, httpMethodType: .GET)
netWorkManager.callAPI { (data, status, error) in
guard let data = data else {
onFail(NetWorkError.otherError)
return
}
switch status {
case 200:
do{
if let responseModel = try JSONDecoder().decode(ResonseModel?.self, from: data) {
onSuccess(responseModel)
}
}catch {
onFail(NetWorkError.otherError)
}
default:
onFail(NetWorkError.otherError)
}
}
The model looks like this:
struct ResonseModel: Codable {
let type : String
let format: String
let data: [String: Champion]
struct Champion: Codable {
let version: String
let id: String
let key: Int
let name: String
let title: String
let blurb: String
}
}
The JSON structure looks like this:
{
"type": "champion",
"format": "standAloneComplex",
"version": "9.3.1",
"data": {
"Aatrox": {
"version": "9.3.1",
"id": "Aatrox",
"key": "266",
"name": "Aatrox",
"title": "the Darkin Blade",
"blurb": "Once honored defenders of Shurima against the Void, Aatrox and his brethren would eventually become an even greater threat to Runeterra, and were defeated only by cunning mortal sorcery. But after centuries of imprisonment, Aatrox was the first to find...",
"info": {
"attack": 8,
"defense": 4,
"magic": 3,
"difficulty": 4
},
"tags": [
"Fighter",
"Tank"
],
"partype": "Blood Well",
},
"Ahri": {
"version": "9.3.1",
"id": "Ahri",
"key": "103",
"name": "Ahri",
"title": "the Nine-Tailed Fox",
"blurb": "Innately connected to the latent power of Runeterra, Ahri is a vastaya who can reshape magic into orbs of raw energy. She revels in toying with her prey by manipulating their emotions before devouring their life essence. Despite her predatory nature...",
"info": {
"attack": 3,
"defense": 4,
"magic": 8,
"difficulty": 5
},
"tags": [
"Mage",
"Assassin"
],
"partype": "Mana",
},
...
this is the link for the JSON if you want to look into it: http://ddragon.leagueoflegends.com/cdn/9.3.1/data/en_US/champion.json
I want to decode the "data" property as a dictionary whose key is the name of the champion and value is the champion. But the jsonDecoder seems doesn't recognize my model structure. It ends up catch the error.
The JSON-parameter "key" is not an Integer.
Change it to String and it will work:
struct ResonseModel: Codable {
let type : String
let format: String
let data: [String: Champion]
struct Champion: Codable {
let version: String
let id: String
let key: String
let name: String
let title: String
let blurb: String
}
}
You can switch to manually decoding Champion in order to clean up the data.
struct ResonseModel: Decodable {
let type : String
let format: String
let data: [String: Champion]
struct Champion: Decodable {
let version: String
let id: String
let key: Int
let name: String
let title: String
let blurb: String
enum CodingKeys: String, CodingKey {
case version, id, key, name, title, blurb
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.version = try container.decode(String.self, forKey: .version)
self.id = try container.decode(String.self, forKey: .id)
guard let key = Int(try container.decode(String.self, forKey: .key)) else {
throw DecodingError.valueNotFound(Int.self,
.init(codingPath: decoder.codingPath,
debugDescription: "Bad value for id"))
}
self.key = key
self.name = try container.decode(String.self, forKey: .name)
self.title = try container.decode(String.self, forKey: .title)
self.blurb = try container.decode(String.self, forKey: .blurb)
}
}
}
This is basically the code that the compiler writes for you; it just converts the string into an int because that's what you really wanted.
Given the following sample JSON
{
"filters": [
{ "name" : "First Type",
"types" : ["md", "b", "pb"]},
{ "name" : "Second Type",
"types" : ["pt", "ft", "t"]},
{ "name" : "Third Type",
"types" : ["c", "r", "s", "f"]
}
],
"jobs": [
{ "title":"f",
"description" : "descrip text",
"criteria":[ "md", "ft", "s" ],
"img" : "www1"
},
{ "title":"boa",
"description" : "a description",
"criteria":[ "b", "pb", "f", "ft" ],
"img" : "www2"
},
{ "title":"BK",
"description" : "something here",
"criteria":[ "md", "pt", "ft", "b", "s" ],
"img" : "www3"
}
]
}
(Using Alamofire to create the response)
let responseJSON : JSON = JSON(response.result.value!)
1) I am trying to convert these into two String arrays. One array: let filter = [String : [String]] and another array for the jobs. How do I do it? (aka give a man a fish) The following are some sample code snippets, but none are even close to working.
let filterCategories = responseJSON["filters"].arrayValue.map({
$0["name"].stringValue
})
and
for (key,subJson):(String, JSON) in responseJSON["filters"] {
let object : filterObject = filterObject(category: key, list: subJson.arrayValue.map({ $0.stringValue }))
}
2) How do I learn how to use this properly? (aka teach a man to fish) I have been reading the documentation (https://github.com/SwiftyJSON/SwiftyJSON) but I'm struggling to understand it. I'm guessing the final answer will use .map, .stringValue, and .arrayValue. Ultimately though I'm trying to avoid lots of needless or unmanageable code.
Swift 4 provides JSON parsing support out of the box - maybe start within something like Ultimate Guide to JSON Parsing with Swift 4
Based on your available structure, I threw into a Playground and used...
// I was loading the JSON from a file within the Playground's Resource folder
// But basically, you want to end up with a reference to Data
let filePath = Bundle.main.path(forResource:"Source", ofType: "json")
let data = FileManager.default.contents(atPath: filePath!)
struct Filter: Codable {
let name: String;
let types: [String];
}
struct Job: Codable {
let title: String;
let description: String;
let criteria: [String];
let img: String;
}
struct Stuff: Codable {
let filters: [Filter];
let jobs: [Job];
}
let decoder = JSONDecoder();
let stuff = try! decoder.decode(Stuff.self, from: data!)
print("Filter:")
for filter in stuff.filters {
print(filter.name)
for type in filter.types {
print(" - \(type)")
}
}
print("Jobs:")
for job in stuff.jobs {
print(job.title)
print(job.description)
print(job.img)
for type in job.criteria {
print(" - \(type)")
}
}
to parse the results
You can implement Codable protocol to parse response. use your json response instead of this
let url = Bundle.main.url(forResource: "data", withExtension: "json")
let data = NSData(contentsOf: url!)
i used this for playground for testing.
struct Root: Codable {
let jobs: [Jobs]
let filters: [Filters]
private enum CodingKeys: String, CodingKey {
case jobs = "jobs"
case filters = "filters"
}
}
struct Filters: Codable {
let name: String?
let typees: String?
}
struct Jobs: Codable {
let title: String?
let description: String?
let criteria: [String]?
let img: String?
}
let url = Bundle.main.url(forResource: "data", withExtension: "json")
let data = NSData(contentsOf: url!)
do {
let root = try JSONDecoder().decode(Root.self, from: data as! Data)
if let name = root.jobs.first?.title {
print(name)
}
} catch let error as NSError {
print(error.description)
}
I am trying to update myself and use modern and recent Swift 4 features.
That is why I am training with the Codable protocol in order to parse JSON and directly map my model object.
First of all, I did some research and self learning.
This article helped me a lot : Ultimate guide
I just need to focus on the "Com" array.
As you can notice, it contains some nested object. I named them Flash Info.
It is defined by :
endDate
text
image[]
title
productionDate
id
So here is my Codable Struct :
struct FlashInfo : Codable {
let productionDate: String
let endDate: String
let text: String
let title: String
let id: String
}
First of all, I was trying to parse it without the array of Images, I will handle it later.
So here is my method :
func getFlashInfo(success: #escaping (Array<FlashInfo>) -> Void) {
var arrayFlash = [FlashInfo]()
Alamofire.request(URL_TEST, method: .get).responseJSON { response in
if response.value != nil {
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
print("Data: \(utf8Text)")
}
//let decoder = JSONDecoder()
// let flash = try! decoder.decode(FlashInfo.self, from: response.data!)
// arrayFlash.append(flash)
success(arrayFlash)
} else {
print("error getFlashInfo")
}
}
}
I don't know how to handle the fact that I only need the "Com" array and how to iterate through all nested objects in order to fill my array in the callback.
I mean, will the decode protocol iterate through each objects ?
Am I clear?
EDIT: JSON as text
{"Test": [], "Toto": [], "Com": [{"endDate": "2017-06-27T08:00:00Z", "text": "John Snow is getting married", "image": ["895745-test.png", "632568-test.png"], "titre": "We need you!", "productionDate": "2017-07-02T16:16:23Z", "id": "9686"}, {"endDate": "2017-07-27T08:00:00Z", "text": "LOL TEST", "image": ["895545-test.png", "632568-test.png"], "titre": "She needs you!", "productionDate": "2017-08-02T16:16:23Z", "id": "9687"},{"endDate": "2017-06-27T08:00:00Z", "text": "iOS swift", "image": ["895775-test.png", "638568-test.png"], "titre": "They need you!", "productionDate": "2017-07-02T16:16:23Z", "id": "9688"}], "Yt": []}
I believe the quickest way is just to define an incomplete Response type as well. For instance:
struct Response: Codable {
let Com: [FlashInfo]
}
struct FlashInfo: Codable {
let productionDate: String
let endDate: String
let text: String
let title: String
let id: String
let image: [String] = [] // Ignored for now.
enum CodingKeys: String, CodingKey {
case productionDate, endDate, text, id
case title = "titre" // Fix for JSON typo ;)
}
}
and decode it like this:
let decoder = JSONDecoder()
let response = try! decoder.decode(Response.self, from: data)
print(response.Com)
This worked great with the test data you provided (just watch out for the typo in the title field):
let json = """
{"Test": [], "Toto": [], "Com": [{"endDate": "2017-06-27T08:00:00Z", "text": "John Snow is getting married", "image": ["895745-test.png", "632568-test.png"], "titre": "We need you!", "productionDate": "2017-07-02T16:16:23Z", "id": "9686"}, {"endDate": "2017-07-27T08:00:00Z", "text": "LOL TEST", "image": ["895545-test.png", "632568-test.png"], "titre": "She needs you!", "productionDate": "2017-08-02T16:16:23Z", "id": "9687"},{"endDate": "2017-06-27T08:00:00Z", "text": "iOS swift", "image": ["895775-test.png", "638568-test.png"], "titre": "They need you!", "productionDate": "2017-07-02T16:16:23Z", "id": "9688"}], "Yt": []}
"""
let data = json.data(using: .utf8)!