I'm trying to parse some json in Xcode that is basically a bunch of objects in an object. The json looks like this below.
{"TDOC": {
"assetType": "EQUITY",
"assetMainType": "EQUITY",
"cusip": "87918A105",
"symbol": "TDOC"}}
I am parsing it using the code below in Xcode using swift5
do {
if let json = try JSONSerialization.jsonObject(with: jData, options: []) as? [String: Any] {
if let pr = json["TDOC"] as? Array<Dictionary<String, Any>> {
for p in pr {
print(p["assetType"] ?? "")
}
}
}
} catch let err {
print(err.localizedDescription)
}
I'm trying to get the assetType value but when I run the code, nothing prints out to the console. When I debug, it seems like Xcode just skips right over my for in loop for that prints the assetType for some reason. Any help on this is appreciated.
You can't treat TDOC object as an Array. As it is a dictionary object, you can take it as Dictionary directly.
You can do it like this.
do {
if let json = try JSONSerialization.jsonObject(with: jData, options: []) as? [String: Any] {
if let pr = json["TDOC"] as? Dictionary<String, Any> {
print(pr["assetType"])
}
}
} catch let err {
print(err.localizedDescription)
}
Try this.
"TDOC" key corresponds to a dictionary value, not an array. No for loop needed as well.
do {
if let json = try JSONSerialization.jsonObject(with: jData, options: []) as? [String: Any] {
if let pr = json["TDOC"] as? [String: Any] {
print(pr["assetType"])
}
}
} catch let err {
print(err.localizedDescription)
}
Hope this helps.
1. Make a reflecting class of your data and make it conform to Codable protocol
import Foundation
// MARK: - Welcome
struct MyObject: Codable {
let tdoc: Tdoc
enum CodingKeys: String, CodingKey {
case tdoc = "TDOC"
}
}
// MARK: - Tdoc
struct Tdoc: Codable {
let assetType, assetMainType, cusip, symbol: String
}
1. Parse it using JSONDecoder:
do {
let myObject = try JSONDecoder().decode(MyObject.self, from: jsonData)
print(myObject.tdoc.assetType)
} catch {
print(error)
}
heres an apple doc for a broader info about Encoding, Decoding and CodingKeys protocols
https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types
Hope this helps :)
Related
I have a JSON file and I want to use [String: Any]. The problem I'm having is that I get an error:
No exact matches in call to instance method 'decode'
I would love to understand where I am wrong.
struct MyModel: Decodable {
let myDictionary: [String: Any]
private enum CodingKeys: String, CodingKey {
case myDictionary
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let data = try container.decode([String: Any].self, forKey: .myDictionary)
self.myDictionary = data
}
}
The problem is that a [String: Any] dictionary is not compliant to the Decodable protocol because of the Any part. Which concrete type is not automatically inferred.
But don't worry, in your case this is not a problem. You have just to read your file content and use JSONSerialization to cast it to [String: Any].
import Foundation
let json = """
{
"object": "customer",
"id": "4yq6txdpfadhbaqnwp3",
"email": "john.doe#example.com",
"metadata": {
"link_id": "linked-id",
"buy_count": 4
}
}
"""
struct Model {
var dictionary: [String: Any]
init() {
dictionary = [:]
}
init(data: Data) {
self.init()
if let dictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] {
self.dictionary = dictionary
}
}
init(string: String) {
self.init()
if let data = string.data(using: .utf8) {
self.init(data: data)
}
}
}
You can then decide if you want to create your Model from a String or Data.
I am trying to decode data from a Firebase DataSnapshot so that it can be decoded using JSONDecoder.
I can decode this data fine when I use a URL to access it with a network request (obtaining a Data object).
However, I want to use the Firebase API to directly obtain the data, using observeSingleEvent as described on this page.
But, when I do this, I cannot seem to convert the result into a Data object, which I need to use JSONDecoder.
Is it possible to do the new style of JSON decoding with a DataSnapshot? How is it possible? I can't seem to figure it out.
I have created a library called CodableFirebase that provides Encoders and Decoders that are designed specifically for Firebase.
So for the example above:
import Firebase
import CodableFirebase
let item: GroceryItem = // here you will create an instance of GroceryItem
let data = try! FirebaseEncoder().encode(item)
Database.database().reference().child("pathToGraceryItem").setValue(data)
And here's how you will read the same data:
Database.database().reference().child("pathToGraceryItem").observeSingleEvent(of: .value, with: { (snapshot) in
guard let value = snapshot.value else { return }
do {
let item = try FirebaseDecoder().decode(GroceryItem.self, from: value)
print(item)
} catch let error {
print(error)
}
})
I've converted Firebase Snapshots using JSONDecoder by converting snapshots back to JSON in Data format. Your struct needs to conform to Decodable or Codable. I've done this with SwiftyJSON but this example is using JSONSerialization and it still works.
JSONSnapshotPotatoes {
"name": "Potatoes",
"price": 5,
}
JSONSnapshotChicken {
"name": "Chicken",
"price": 10,
"onSale": true
}
struct GroceryItem: Decodable {
var name: String
var price: Double
var onSale: Bool? //Use optionals for keys that may or may not exist
}
Database.database().reference().child("grocery_item").observeSingleEvent(of: .value, with: { (snapshot) in
guard let value = snapshot.value as? [String: Any] else { return }
do {
let jsonData = try JSONSerialization.data(withJSONObject: value, options: [])
let groceryItem = try JSONDecoder().decode(GroceryItem.self, from: jsonData)
print(groceryItem)
} catch let error {
print(error)
}
})
Please note that if your JSON keys are not the same as your Decodable struct. You'll need to use CodingKeys. Example:
JSONSnapshotSpinach {
"title": "Spinach",
"price": 10,
"onSale": true
}
struct GroceryItem: Decodable {
var name: String
var price: Double
var onSale: Bool?
enum CodingKeys: String, CodingKey {
case name = "title"
case price
case onSale
}
}
You can find more information on this using Apple Docs here.
No. Firebase returns a FIRDataSnapshot that can't be decodable. You can use this structure however, which is pretty simple and easy to understand:
struct GroceryItem {
let key: String
let name: String
let addedByUser: String
let ref: FIRDatabaseReference?
var completed: Bool
init(name: String, addedByUser: String, completed: Bool, key: String = "") {
self.key = key
self.name = name
self.addedByUser = addedByUser
self.completed = completed
self.ref = nil
}
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as! [String: AnyObject]
name = snapshotValue["name"] as! String
addedByUser = snapshotValue["addedByUser"] as! String
completed = snapshotValue["completed"] as! Bool
ref = snapshot.ref
}
func toAnyObject() -> Any {
return [
"name": name,
"addedByUser": addedByUser,
"completed": completed
]
}
}
And use toAnyObject() to save your item:
let groceryItemRef = ref.child("items")
groceryItemRef.setValue(groceryItem.toAnyObject())
Source: https://www.raywenderlich.com/139322/firebase-tutorial-getting-started-2
Or you can use this solution for children
extension DatabaseReference {
func makeSimpleRequest<U: Decodable>(completion: #escaping (U) -> Void) {
self.observeSingleEvent(of: .value, with: { snapshot in
guard let object = snapshot.children.allObjects as? [DataSnapshot] else { return }
let dict = object.compactMap { $0.value as? [String: Any] }
do {
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: [])
let parsedObjects = try JSONDecoder().decode(U.self, from: jsonData)
completion(parsedObjects)
} catch let error {
print(error)
}
})
}
}
and use
self.refPriceStatistics.child(productId).makeSimpleRequest { (parsedArray: [YourArray]) in
callback(parsedArray)
}
If your data type is Codable you can use the following solution to decode directly. You do not need any plugin. I used the solution for Cloud Firestore.
import Firebase
import FirebaseFirestoreSwift
let db = Firestore.firestore()
let query = db.collection("CollectionName")
.whereField("id", isEqualTo: "123")
guard let documents = snapshot?.documents, error == nil else {
return
}
if let document = documents.first {
do {
let decodedData = try document.data(as: ModelClass.self)
// ModelClass a Codable Class
}
catch let error {
//
}
}
You can convert the value returned by Firebase to Data, and then decode that.
Add this extension to your project:
extension Collection {
//Designed for use with Dictionary and Array types
var jsonData: Data? {
return try? JSONSerialization.data(withJSONObject: self, options: .prettyPrinted)
}
}
Then use it to convert the value of the observed snapshot into data, which can then be decoded:
yourRef.observe(.value) { (snapshot) in
guard snapshot.exists(),
let value = snapshot.value as? [String],
let data = value.jsonData else {
return
}
//cast to expected type
do {
let yourNewObject = try JSONDecoder().decode([YourClass].self, from: data)
} catch let decodeError {
print("decodable error")
}
}
You can use this library CodableFirebase or the following extension can be helpful.
extension JSONDecoder {
func decode<T>(_ type: T.Type, from value: Any) throws -> T where T : Decodable {
do {
let data = try JSONSerialization.data(withJSONObject: value, options: .prettyPrinted)
let decoded = try decode(type, from: data)
return decoded
} catch {
throw error
}
}
Trying to sort the array of JSON objects in the array by "name" alphabetically. Gives error of "Value of type 'IndividualContact' has no subscripts" Not sure what that means. Any help is appreciated.
class Contacts{
var contact = [IndividualContact]()
init?(data2: Data) {
do {
if let json2 = try JSONSerialization.jsonObject(with: data2) as? [String: Any], let body = json2["data2"] as? [String: Any] {
if let contacts = body["contacts"] as? [[String: Any]] {
self.contact = ( contacts.map { IndividualContact(json2: $0) } )
}
}
contact.sorted(by: { ($0["name"] as! String) < ($1["name"] as! String) })
} catch {
print("Error deserializing JSON: \(error)")
return nil
}
}
}
The compiler is telling you that your object (IndividualContact) doesn't provide an implementation for the subscript token, i.e. [...]. While you could provide that implementation in your struct/class, doing so might be overkill in this situation. You probably just want to call $0.name in your sorted(by:) closure:
contact.sorted(by: { $0.name < $1.name })
If you really do want to use the $0["name"] syntax, then you can provide such an implementation. It might look something like this:
subscript(key: String) -> String {
get {
switch key {
case "name":
return self.name
default:
return ""
}
}
set {
...
}
}
More info on subscripts can be found here: https://docs.swift.org/swift-book/LanguageGuide/Subscripts.html
I am trying to decode data from a Firebase DataSnapshot so that it can be decoded using JSONDecoder.
I can decode this data fine when I use a URL to access it with a network request (obtaining a Data object).
However, I want to use the Firebase API to directly obtain the data, using observeSingleEvent as described on this page.
But, when I do this, I cannot seem to convert the result into a Data object, which I need to use JSONDecoder.
Is it possible to do the new style of JSON decoding with a DataSnapshot? How is it possible? I can't seem to figure it out.
I have created a library called CodableFirebase that provides Encoders and Decoders that are designed specifically for Firebase.
So for the example above:
import Firebase
import CodableFirebase
let item: GroceryItem = // here you will create an instance of GroceryItem
let data = try! FirebaseEncoder().encode(item)
Database.database().reference().child("pathToGraceryItem").setValue(data)
And here's how you will read the same data:
Database.database().reference().child("pathToGraceryItem").observeSingleEvent(of: .value, with: { (snapshot) in
guard let value = snapshot.value else { return }
do {
let item = try FirebaseDecoder().decode(GroceryItem.self, from: value)
print(item)
} catch let error {
print(error)
}
})
I've converted Firebase Snapshots using JSONDecoder by converting snapshots back to JSON in Data format. Your struct needs to conform to Decodable or Codable. I've done this with SwiftyJSON but this example is using JSONSerialization and it still works.
JSONSnapshotPotatoes {
"name": "Potatoes",
"price": 5,
}
JSONSnapshotChicken {
"name": "Chicken",
"price": 10,
"onSale": true
}
struct GroceryItem: Decodable {
var name: String
var price: Double
var onSale: Bool? //Use optionals for keys that may or may not exist
}
Database.database().reference().child("grocery_item").observeSingleEvent(of: .value, with: { (snapshot) in
guard let value = snapshot.value as? [String: Any] else { return }
do {
let jsonData = try JSONSerialization.data(withJSONObject: value, options: [])
let groceryItem = try JSONDecoder().decode(GroceryItem.self, from: jsonData)
print(groceryItem)
} catch let error {
print(error)
}
})
Please note that if your JSON keys are not the same as your Decodable struct. You'll need to use CodingKeys. Example:
JSONSnapshotSpinach {
"title": "Spinach",
"price": 10,
"onSale": true
}
struct GroceryItem: Decodable {
var name: String
var price: Double
var onSale: Bool?
enum CodingKeys: String, CodingKey {
case name = "title"
case price
case onSale
}
}
You can find more information on this using Apple Docs here.
No. Firebase returns a FIRDataSnapshot that can't be decodable. You can use this structure however, which is pretty simple and easy to understand:
struct GroceryItem {
let key: String
let name: String
let addedByUser: String
let ref: FIRDatabaseReference?
var completed: Bool
init(name: String, addedByUser: String, completed: Bool, key: String = "") {
self.key = key
self.name = name
self.addedByUser = addedByUser
self.completed = completed
self.ref = nil
}
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as! [String: AnyObject]
name = snapshotValue["name"] as! String
addedByUser = snapshotValue["addedByUser"] as! String
completed = snapshotValue["completed"] as! Bool
ref = snapshot.ref
}
func toAnyObject() -> Any {
return [
"name": name,
"addedByUser": addedByUser,
"completed": completed
]
}
}
And use toAnyObject() to save your item:
let groceryItemRef = ref.child("items")
groceryItemRef.setValue(groceryItem.toAnyObject())
Source: https://www.raywenderlich.com/139322/firebase-tutorial-getting-started-2
Or you can use this solution for children
extension DatabaseReference {
func makeSimpleRequest<U: Decodable>(completion: #escaping (U) -> Void) {
self.observeSingleEvent(of: .value, with: { snapshot in
guard let object = snapshot.children.allObjects as? [DataSnapshot] else { return }
let dict = object.compactMap { $0.value as? [String: Any] }
do {
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: [])
let parsedObjects = try JSONDecoder().decode(U.self, from: jsonData)
completion(parsedObjects)
} catch let error {
print(error)
}
})
}
}
and use
self.refPriceStatistics.child(productId).makeSimpleRequest { (parsedArray: [YourArray]) in
callback(parsedArray)
}
If your data type is Codable you can use the following solution to decode directly. You do not need any plugin. I used the solution for Cloud Firestore.
import Firebase
import FirebaseFirestoreSwift
let db = Firestore.firestore()
let query = db.collection("CollectionName")
.whereField("id", isEqualTo: "123")
guard let documents = snapshot?.documents, error == nil else {
return
}
if let document = documents.first {
do {
let decodedData = try document.data(as: ModelClass.self)
// ModelClass a Codable Class
}
catch let error {
//
}
}
You can convert the value returned by Firebase to Data, and then decode that.
Add this extension to your project:
extension Collection {
//Designed for use with Dictionary and Array types
var jsonData: Data? {
return try? JSONSerialization.data(withJSONObject: self, options: .prettyPrinted)
}
}
Then use it to convert the value of the observed snapshot into data, which can then be decoded:
yourRef.observe(.value) { (snapshot) in
guard snapshot.exists(),
let value = snapshot.value as? [String],
let data = value.jsonData else {
return
}
//cast to expected type
do {
let yourNewObject = try JSONDecoder().decode([YourClass].self, from: data)
} catch let decodeError {
print("decodable error")
}
}
You can use this library CodableFirebase or the following extension can be helpful.
extension JSONDecoder {
func decode<T>(_ type: T.Type, from value: Any) throws -> T where T : Decodable {
do {
let data = try JSONSerialization.data(withJSONObject: value, options: .prettyPrinted)
let decoded = try decode(type, from: data)
return decoded
} catch {
throw error
}
}
I'm trying to parse a json but I have some difficulties with the data types and notably the AnyObject type + downcasting.
Let's consider the following json (it's an extract of a full json).
{ "weather":
[
{
"id":804,
"main":"Clouds",
"description":"overcast clouds",
"icon":"04d"
}
],
}
To me, the json can be described as follow :
- json: Dictionary of type [String: AnyObject] (or NSDictionary, so = [NSObject, AnyObject] in Xcode 6 b3)
- "weather": Array of type [AnyObject] (or NSArray)
- Dictionary of type [String: AnyObject] (or NSDictionary, so = [NSObject, AnyObject] in Xcode 6 b3)
My json is of type AnyObject! (I use JSONObjectWithData to get the JSON from a URL).
I then want to access the weather Dictionary. Here is the code I wrote.
var localError: NSError?
var json: AnyObject! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &localError)
if let dict = json as? [String: AnyObject] {
if let weatherDictionary = dict["weather"] as? [AnyObject] {
// Do stuff with the weatherDictionary
}
}
Here is the error I got
Playground execution failed: error: <EXPR>:28:56: error: '[AnyObject]' is not a subtype of '(String, AnyObject)'
if let weatherDictionary = dict["weather"] as? [AnyObject] {
I don't understand why dict["weather"] is compared to a subtype of (String, AnyObject) and not AnyObject.
I declared my dictionary as [String: AnyObject], so I i access a value using the String key, I should have an AnyObject, no ?
If I use NSDictionary instead of [String: AnyObject], it works.
If I use NSArray instead of [AnyObject], it works.
- The Xcode 6 beta 3 release notes tell that "NSDictionary* is now imported from Objective-C APIs as [NSObject : AnyObject].".
- And the Swift book: "When you bridge from an NSArray object to a Swift array, the resulting array is of type [AnyObject]."
EDIT
I forgot to force unwrapping the dict["weather"]!.
if let dict = json as? [String: AnyObject] {
println(dict)
if let weatherDictionary = dict["weather"]! as? [AnyObject] {
println("\nWeather dictionary:\n\n\(weatherDictionary)")
if let descriptionString = weatherDictionary[0]["description"]! as? String {
println("\nDescription of the weather is: \(descriptionString)")
}
}
}
Note that we should double check for the existence of the first Optional.
if let dict = json as? [String: AnyObject] {
for key in ["weather", "traffic"] {
if let dictValue = dict[key] {
if let subArray = dictValue as? [AnyObject] {
println(subArray[0])
}
} else {
println("Key '\(key)' not found")
}
}
}
This works fine for me in the playground and in the terminal using env xcrun swift
UPDATED FOR SWIFT 4 AND CODABLE
Here is a Swift 4 example using the Codable protocol.
var jsonStr = "{\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],}"
struct Weather: Codable {
let id: Int
let main: String
let description: String
let icon: String
}
struct Result: Codable {
let weather: [Weather]
}
do {
let weather = try JSONDecoder().decode(Result.self, from: jsonStr.data(using: .utf8)!)
print(weather)
}
catch {
print(error)
}
UPDATED FOR SWIFT 3.0
I have updated the code for Swift 3 and also showed how to wrap the parsed JSON into objects. Thanks for all the up votes!
import Foundation
struct Weather {
let id: Int
let main: String
let description: String
let icon: String
}
extension Weather {
init?(json: [String: Any]) {
guard
let id = json["id"] as? Int,
let main = json["main"] as? String,
let description = json["description"] as? String,
let icon = json["icon"] as? String
else { return nil }
self.id = id
self.main = main
self.description = description
self.icon = icon
}
}
var jsonStr = "{\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],}"
enum JSONParseError: Error {
case notADictionary
case missingWeatherObjects
}
var data = jsonStr.data(using: String.Encoding.ascii, allowLossyConversion: false)
do {
var json = try JSONSerialization.jsonObject(with: data!, options: [])
guard let dict = json as? [String: Any] else { throw JSONParseError.notADictionary }
guard let weatherJSON = dict["weather"] as? [[String: Any]] else { throw JSONParseError.missingWeatherObjects }
let weather = weatherJSON.flatMap(Weather.init)
print(weather)
}
catch {
print(error)
}
-- Previous Answer --
import Foundation
var jsonStr = "{\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],}"
var data = jsonStr.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: false)
var localError: NSError?
var json: AnyObject! = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: &localError)
if let dict = json as? [String: AnyObject] {
if let weather = dict["weather"] as? [AnyObject] {
for dict2 in weather {
let id = dict2["id"]
let main = dict2["main"]
let description = dict2["description"]
println(id)
println(main)
println(description)
}
}
}
Since I'm still getting up-votes for this answer, I figured I would revisit it for Swift 2.0:
import Foundation
var jsonStr = "{\"weather\":[{\"id\":804,\"main\":\"Clouds\",\"description\":\"overcast clouds\",\"icon\":\"04d\"}],}"
var data = jsonStr.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: false)
do {
var json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
if let dict = json as? [String: AnyObject] {
if let weather = dict["weather"] as? [AnyObject] {
for dict2 in weather {
let id = dict2["id"] as? Int
let main = dict2["main"] as? String
let description = dict2["description"] as? String
print(id)
print(main)
print(description)
}
}
}
}
catch {
print(error)
}
The biggest difference is that the variable json is no longer an optional type and the do/try/catch syntax. I also went ahead and typed id, main, and description.
Try:
https://github.com/dankogai/swift-json
With it you can go like this:
let obj:[String:AnyObject] = [
"array": [JSON.null, false, 0, "", [], [:]],
"object":[
"null": JSON.null,
"bool": true,
"int": 42,
"double": 3.141592653589793,
"string": "a α\t弾\n𪚲",
"array": [],
"object": [:]
],
"url":"http://blog.livedoor.com/dankogai/"
]
let json = JSON(obj)
json.toString()
json["object"]["null"].asNull // NSNull()
json["object"]["bool"].asBool // true
json["object"]["int"].asInt // 42
json["object"]["double"].asDouble // 3.141592653589793
json["object"]["string"].asString // "a α\t弾\n𪚲"
json["array"][0].asNull // NSNull()
json["array"][1].asBool // false
json["array"][2].asInt // 0
json["array"][3].asString // ""
Using my library (https://github.com/isair/JSONHelper) you can do this with your json variable of type AnyObject:
var weathers = [Weather]() // If deserialization fails, JSONHelper just keeps the old value in a non-optional variable. This lets you assign default values like this.
if let jsonDictionary = json as? JSONDictionary { // JSONDictionary is an alias for [String: AnyObject]
weathers <-- jsonDictionary["weather"]
}
Had your array not been under the key "weather", your code would have been just this:
var weathers = [Weather]()
weathers <-- json
Or if you have a json string in your hands you can just pass it as well, instead of creating a JSON dictionary from the string first. The only setup you need to do is writing a Weather class or struct:
struct Weather: Deserializable {
var id: String?
var name: String?
var description: String?
var icon: String?
init(data: [String: AnyObject]) {
id <-- data["id"]
name <-- data["name"]
description <-- data["description"]
icon <-- data["icon"]
}
}