Swift nested JSON Objects not decoding - json

I'm trying to decode an array of nested objects within Swift and it's not working. The nested objects are of the same type which I'm using Realm to store the values and look like:
struct myObject: Codable {
var name: String?
let otherObjects = List<myObject>()
var events: [String]?
...
if let objects = try container.decodeIfPresent([myObject].self, forKey: .otherObjects) {
otherObjects.append(objectsIn: objects)
}
}
Decoder Function - This works for all other fields inside the object at top level including events which is an array of String.
static func decodeResponse<T:Codable>(_ response : Any?) -> T? {
if let response = response, let dict = response as? [String:Any] {
var dictionaryToParse = dict
var arrayToParse: [[String:Any]]?
if let dataObject = dict["data"] as? [String:Any] {
//sometimes the response has an outer "data" OBJECT
dictionaryToParse = dataObject
} else if let dataArray = dict["data"] as? [[String:Any]] {
//sometimes the response has an outer "data" ARRAY
arrayToParse = dataArray
}
do {
guard let data = try? JSONSerialization.data(withJSONObject: arrayToParse ?? dictionaryToParse, options: []) else { return nil }
let obj = try JSONDecoder().decode(T.self, from: data)
print("DECODE: \(obj)")
return obj
}
catch let error {
print("ERROR: (decodeResponse): \(error)")
return nil
}
}
return nil
}
The coding keys map as they're the same type and nothing gets outputted as an error.

Related

Decoding JSON with variable parameters using Codable

I have a json response like this
{
"name":"test",
"params":{
"param1":"testA",
"param2":4055,
"param3":9593.34959,
"question":"is this is a test?",
"anything":"testing?",
"random":true
},
"price":103.3
}
My codable struct looks like this
struct:Codable {
var name:String
var params:[String:String]?
var price:Double
}
I have set params to optional because sometimes there are no params but a lot of times there are and codable has an issue because I don't know what types the values in the params dictionary are. I don't even know what the keys are sometimes. I just want to parse them as a dictionary of keys and values with values of type Bool, Int, Double, or String. so a dict like this
let params = ["paramA":1, "param2":"test", "param3":true]
or in the case of the above json this:
let params = ["param1":"testA", "param2":4055, "param3": 9593.34959, "question":"is this is a test?", "anything":"testing?", "random":true]
I am pretty sure I have to create a custom decoder but not exactly sure how to do it.
In your case it's easier to decode json manually like so:
public enum SerializationError: Error {
case wrongRootElement(String)
case missing(String)
case invalid(String, Any)
}
struct YourStruct {
var name:String
var params:[String: String]?
var price:Double
}
extension YourStruct {
public init(json: Any) throws {
guard let jsonDict = json as? [String: Any] else {
throw SerializationError.wrongRootElement("Expected json dictionary not \(json)")
}
guard let name = jsonDict["name"] as? String else {
throw SerializationError.missing("name")
}
let params = jsonDict["params"] as? [String: Any]
let paramsStrs = params?.reduce(into: [String: String]()) { (result, keyValue) in
result[keyValue.key] = String(describing: keyValue.value)
}
guard let price = jsonDict["price"] as? Double else {
throw SerializationError.missing("price")
}
self.init(name: name, params: paramsStrs, price: price)
}
}
let anyYourStruct = try? JSONSerialization.jsonObject(with: jsonData, options: [])
let yourStruct = try? YourStruct(json: anyYourStruct)

filtering JSON data in swift

I am trying to parse my JSON data and append only those objects into array which meets specified condition. At the moment I have commented out code which fetch all objects from the API and add them into array. However, I would like to limit it so that it only appends objects with "wger.de" value for "license_author" key.
However I am getting error on line:
if eachExercise["license_author"] == "wger.de"
Binary operator '==' cannot be applied to operands of type 'Any?' and 'String'.
However I still wants to keep it as Any object because I would like to fetch both strings and integers data from my API.
This is the code for my parseData() function:
func parseData() {
fetchedExercise = []
let urlPath = "https://wger.de/api/v2/exercise/?format=json&language=2&status=2"
let url = URL(string: urlPath)!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Error while parsing JSON")
}
else {
do {
if let data = data,
let fetchedData = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as? [String:Any],
let exercises = fetchedData["results"] as? [[String: Any]] {
// WORKING CODE
/*
for eachExercise in exercises
{
let name = eachExercise["name"] as! String
let description = eachExercise["description"] as! String
self.fetchedExercise.append(Exercise(name: name, description: description))
}
*/
// TESTING
for eachExercise in exercises {
if eachExercise["license_author"] == "wger.de" {
let name = eachExercise["name"] as! String
let description = eachExercise["description"] as! String
let id = eachExercise["id"] as! Int
self.fetchedExercise.append(Exercise(name: name, description: description))
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
catch {
print("Error while parsing data.")
}
}
}
task.resume()
}
Use the where clause and optional downcast Any to String
for eachExercise in exercises where eachExercise["license_author"] as? String == "wger.de" { ...
You need cast it to String.
if eachExercise["license_author"] as? String == "wger.de" {
}

Get a type of variable and declare an other variable with this type in Swift

I currently exploring the JSON world in swift and I've some trouble to make a clean code.
Let's say I've the following structure
struct Foo {
var a: String = ""
var b: Int = 0
}
I use reflection to get a dictionary of the label : value of this struct with this function:
static func dictionaryRepresentation() -> [String : AnyObject] {
var dictionnary = [String : AnyObject]()
for child in Mirror(reflecting: self).children {
dictionnary[child.label!] = child.value as AnyObject
}
return dictionnary
}
Now I have a dictionary of [String : AnyObject] and here comes the issue.
I would like to be able to do something like this
class JSONManager {
class func decode<T>(fromData data: Data) -> [T]? where T : JSONModelProtocol {
let jsonObject = try? JSONSerialization.jsonObject(with: data,
options: [])
guard let jsonDictionary = jsonObject as? [AnyObject] else {
return nil
}
guard let json = jsonDictionary as? [[String : Any]] else {
return nil
}
let representation = T.dictionaryRepresentation()
for jsonItem in json { // json is a dictionary [String : Any]
for (label, value) in object {
let type = Mirror.init(reflecting: value).subjectType // The type of the item
// Same whit let type = type(of: value)
let item = jsonItem[label] as? type // This line doesn't work I cannot cast here
}
}
}
Any idea about how to achieve this?

TableView Json Swift

I currently developing an app which list user object by making an request to a webservice, which send a response in JSON in this format :
{
"0":
{
"id":"30",
"title":"galaxys6",
"price":"550",
"description":"neuf",
"addedDate":"2015-07-16 15:04:24",
"user_id":"2",
"user_name":"",
"user_zipCode":"69003",
"category_id":"1",
"category_label":"PHONE",
"subcategory_id":"1",
"subcategory_label":"Phone",
"picture":"",
"bdd":{},
"picture_url":"http:\/\/jdl-barreme-orange.dyndns.org\/WEBSERVICE\/pictures\/galaxy s6.JPG"
},
"1":
{
"id":"31",
"title":"iphone4",
"price":"570",
"description":"neuf",
"addedDate":"2015-07-16 15:14:54",
"user_id":"2",
"user_name":"",
"user_zipCode":"69003",
"category_id":"1",
"category_label":"PHONE",
"subcategory_id":"1",
"subcategory_label":"Phone",
"picture":"",
"bdd":{},
"picture_url":"http:\/\/jdl-barreme-orange.dyndns.org\/WEBSERVICE\/pictures\/iphone.JPG"
},
}
For each object my webservice create a dictionary (0;1;2;3....)
I search a method to retrieve for each dictionary the value title and price and put them in a tableView.
Code I used (tableviewcontroller) :
if let jsonData:NSDictionary = NSJSONSerialization.JSONObjectWithData(urlData!, options:NSJSONReadingOptions.MutableContainers , error: &error) as? NSDictionary{
// 4
if let resp = jsonData["1"] as? [NSDictionary] {
NSLog("%#", resp)
// 5
for item in resp {
repositories.append(Repository(jsonData: item))
}
repository controller :
class Repository {
var name: String?
var description: String?
var html_url: String?
init(jsonData: NSDictionary) {
self.name = jsonData["id"] as? String
self.description = jsonData["description"] as? String
self.html_url = jsonData["title"] as? String
}
}
But it doesn't work, I put a breakpoint, and xcode stop to interpret here :
if let resp = jsonData["1"] as? [NSDictionary] {
NSLog("%#", resp)
What am I doing wrong?
Thank you.
Here's how to get the title and price for your JSON:
if let json = NSJSONSerialization.JSONObjectWithData(urlData!, options: nil, error: nil) as? [String:AnyObject] {
for (_, value) in json {
if let dict = value as? [String:AnyObject] {
if let title = dict["title"] as? String {
println(title)
}
if let price = dict["price"] as? String {
println(price)
}
}
}
}
This can also be used to init your Repository classes if you want:
class Repository {
var name: String?
var description: String?
var html_url: String?
init(jsonData: [String:AnyObject]) {
self.name = jsonData["id"] as? String
self.description = jsonData["description"] as? String
self.html_url = jsonData["title"] as? String
}
}
var repos = [Repository]()
if let json = NSJSONSerialization.JSONObjectWithData(urlData!, options: nil, error: nil) as? [String:AnyObject] {
for (_, value) in json {
if let dict = value as? [String:AnyObject] {
let repo = Repository(jsonData: dict)
repos.append(repo)
}
}
}
for repo in repos {
println(repo.name)
println(repo.description)
println(repo.html_url)
}
In the loop I'm ignoring the key: for (_, value) in json but you can use it if needed of course:
for (key, value) in json {
println(key) // "1", "2", ...
// ...
}
UPDATE:
Following your comment asking how to use this answer if your data format is different: if you want an array of dictionaries, change the typecast of the NSJSONSerialization result to reflect that: [[String:AnyObject]]. Next you can iterate over your array to get each dictionary properties:
if let jsonArray = NSJSONSerialization.JSONObjectWithData(urlData!, options: nil, error: nil) as? [[String:AnyObject]] {
for dict in jsonArray {
if let title = dict["title"] as? String {
println(title)
}
}
}
You are making a mistake here
if let resp = jsonData["1"] as? [NSDictionary]
This should be a NSDictionary not [NSDictionary], (which would be an array of dictionaries).
Also this conditional block
if let reposArray = jsonData["items"] as? [NSDictionary]
will never be executed because jsonData does not contain a key "items".
I guess it is the [NSDictionary]
if let resp = jsonData["1"] as? [NSDictionary]
[NSDictionary] is array of NSDictionary same as Array<NSDictionary>
just remove the brackets [] and change to
if let resp = jsonData["1"] as? NSDictionary

Grab data from JSON file doesn't work

I try to grab data from JSON (http://www.openligadb.de/api/getmatchdata/bl1/2014/15). I want to get every single game with the goals, location, team ...
I tried this but it won't work.
let url = "http://www.openligadb.de/api/getmatchdata/bl1/2014/15"
//parse url
if let JSONData = NSData(contentsOfURL: NSURL(string: url)!) {
if let json = (try? NSJSONSerialization.JSONObjectWithData(JSONData, options: [])) as? NSDictionary {
//handle json
}
}
It doesn't steps in the 2nd if-statement (if let json = (try?...).
I hope you could help me.
Edit get data of dictionaries:
//Data Team1
if let team1 = object["Team1"] as? NSDictionary {
if let name = team1["TeamName"] as? String {
print("Name Team1: \(name)")
}
if let logo = team1["TeamIconUrl"] as? String {
print("Logo Team1: \(logo)")
}
// Etc.
}
What you need to do is to understand your JSON structure: you have an array first, not a dictionary.
This array has dictionaries, each of them holding an array of dictionaries.
It may sound complex but it's actually simple, you just follow the structure of your JSON and decode the values with the correct type.
In JSON, an array starts with [ and a dictionary starts with { (also, be careful not to confuse this JSON syntax with Swift's arrays and dictionaries one).
Your code could be something like this, for example:
do {
let url = "http://www.openligadb.de/api/getmatchdata/bl1/2014/15"
if let url = NSURL(string: url),
JSONData = NSData(contentsOfURL: url),
jsonArray = try NSJSONSerialization.JSONObjectWithData(JSONData, options: []) as? NSArray {
for object in jsonArray {
if let goalsArray = object["Goals"] as? NSArray {
// Each "goal" is a dictionary
for goal in goalsArray {
print(goal)
if let name = goal["GoalGetterName"] as? String {
print("Name: \(name)")
}
if let ID = goal["GoalID"] as? Int {
print("ID: \(ID)")
}
// Etc.
}
}
}
}
} catch {
print(error)
}
UPDATE: you're almost there! But "Team1" is a dictionary, not an array. :)
Here's the solution:
do {
let url = "http://www.openligadb.de/api/getmatchdata/bl1/2014/15"
if let url = NSURL(string: url),
JSONData = NSData(contentsOfURL: url),
jsonArray = try NSJSONSerialization.JSONObjectWithData(JSONData, options: []) as? NSArray {
for object in jsonArray {
if let team1 = object["Team1"] as? NSDictionary {
if let name = team1["TeamName"] as? String {
print("Name Team1: \(name)")
}
if let logo = team1["TeamIconUrl"] as? String {
print("Logo Team1: \(logo)")
}
}
}
}
} catch {
print(error)
}