Parsing json to swift constants - json

I am trying to parse all the data from the endpoint and assign it to constants so I can use them in my ViewController class. My biggest problem is assigning each item of the "key" array to constants. Can anyone help me out?
static func fetchRates(completionHandler: (current: [Currency]) -> ()) {
let urlString = "https://api.bitcoinaverage.com/ticker/all"
let url = NSURL(string: urlString)
NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (location, response, error) -> Void in
do {
let json = try(NSJSONSerialization.JSONObjectWithData(location!, options: .MutableContainers))
let tickerData = [Currency]()
for key in json as! [String : AnyObject] {
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completionHandler(current: tickerData)
})
} catch let err as NSError {
print(err)
}
}).resume()
}
This is the response from the endpoint.
{
"AUD": {
"24h_avg": 621.17,
"ask": 624.12,
"bid": 620.3,
"last": 620.45,
"timestamp": "Mon, 23 May 2016 20:01:16 -0000",
"total_vol": 671.28
},
"BRL": {
"24h_avg": 1725.77,
"ask": 1748.83,
"bid": 1731.8,
"last": 1738.64,
"timestamp": "Mon, 23 May 2016 20:01:16 -0000",
"total_vol": 534.19
},
"CAD": {
"24h_avg": 579.2,
"ask": 579.27,
"bid": 573.57,
"last": 577.42,
"timestamp": "Mon, 23 May 2016 20:01:16 -0000",
"total_vol": 413.81
},

Give this a try:
for (key, value) in (json as! [String: AnyObject]) {
let currencyData = value as! [String: AnyObject]
// key is the currency code: AUD, BRL, etc
// currencyData is the details: ask, bid, last, ...
}

Here's a rough and ready solution, but it's a start you can build on.
Let's start by defining some convenience types:
// Define a handy result type
// See https://github.com/antitypical/Result if you want a more detailed implementation.
public enum Result<T> {
case Success(T)
case Error(ErrorType)
}
// Define an error type for inconsistentData
public enum DataError: ErrorType {
case NoData
}
// Define a struct to hold a Currency
public struct Currency: CustomStringConvertible, Equatable {
let currencyCode: String
let dayAverage: Double
let ask: Double
let bid: Double
let last: Double
let timestamp: String
let totalVolume: Double
// This makes it easier to debug
public var description: String {
return "currencyCode: \(currencyCode), dayAverage: \(dayAverage), ask: \(ask), bid: \(bid), last: \(last), timestamp: \(timestamp), totalVolume: \(totalVolume)"
}
public init(currencyCode: String,
dayAverage: Double,
ask: Double,
bid: Double,
last: Double,
timestamp: String,
totalVolume: Double) {
self.currencyCode = currencyCode
self.dayAverage = dayAverage
self.ask = ask
self.bid = bid
self.last = last
self.timestamp = timestamp
self.totalVolume = totalVolume
}
}
// Make sure your structs conform to Equatable.
public func == (lhs: Currency, rhs: Currency) -> Bool {
return
lhs.currencyCode == rhs.currencyCode &&
lhs.dayAverage == rhs.dayAverage &&
lhs.ask == rhs.ask &&
lhs.bid == rhs.bid &&
lhs.last == rhs.last &&
lhs.timestamp == rhs.timestamp &&
lhs.totalVolume == rhs.totalVolume
}
// I like to define convenience initialisers outside of the struct. Keeps things less coupled.
public extension Currency {
public init?(currencyCode: String, values: [String : AnyObject]) {
guard
let dayAverage = values["24h_avg"] as? Double,
let ask = values["ask"] as? Double,
let bid = values["bid"] as? Double,
let last = values["last"] as? Double,
let timestamp = values["timestamp"] as? String,
let totalVolume = values["total_vol"] as? Double
else { return nil }
self = Currency(currencyCode: currencyCode,
dayAverage: dayAverage,
ask: ask,
bid: bid,
last: last,
timestamp: timestamp,
totalVolume: totalVolume)
}
}
Now the function to fetch the values can be written as:
// The function to fetch the currencies.
// Use a richer type for the completion parameter. It is either a success with a list of currencies or an error.
public func fetchRates(completionHandler: (Result<[Currency]>) -> ()) {
let url = NSURL(string: "https://api.bitcoinaverage.com/ticker/all")! // Force unwrapped as we assume this is a valid URL
NSURLSession.sharedSession().dataTaskWithURL(url) { (data, _, error) in
if let error = error {
completionHandler(.Error(error))
return
}
do {
guard
let data = data,
// Being generous with the type of the result because there is a top level key that is the timestamp not related to a currency.
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers) as? [String : AnyObject]
else {
completionHandler(.Error(DataError.NoData))
return
}
var currencies = [Currency]()
for (key, value) in json {
guard
// Make sure that the values we are about to pass on are of the correct type.
let value = value as? [String : AnyObject],
let currency = Currency(currencyCode: key, values: value)
else { continue }
currencies.append(currency)
}
print(currencies)
completionHandler(.Success(currencies))
} catch {
completionHandler(.Error(error))
}
}.resume()
}
And you can run this function with the completion handlers as:
fetchRates { result in
dispatch_async(dispatch_get_main_queue()) {
switch result {
case .Success(let currencies):
for currency in currencies {
print(currency)
}
case .Error(let error):
print(error)
}
}
}
You can download the playground where you can see all this working at:
https://dl.dropboxusercontent.com/u/585261/Currency.playground.zip

Related

JSONDecoder unable to decode an object encoded by JSONEncoder

I have a requirement to encode/decode snakeCased JSONs. I found that encoder encodes Value2 object correctly, however decoder fails to decode it. What I do wrong here?
Required Json format:
{
"address_line_1" : "Address",
"full_name" : "Name",
"id" : 2
}
Code:
struct Value1: Codable {
let id: Int
let fullName: String
let addressLine1: String
}
struct Value2: Codable {
let id: Int
let fullName: String
let addressLine_1: String
}
func printJson(_ object: Data) throws {
let json = try JSONSerialization.jsonObject(with: object, options: [])
let data = try JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted, .sortedKeys])
print(String(data: data, encoding: .utf8)!)
}
func encode<T: Encodable>(_ object: T) throws -> Data {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
return try encoder.encode(object)
}
func decode<T: Decodable>(_ type: T.Type, from data: Data) throws {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
_ = try decoder.decode(type, from: data)
print("✅ Decoded \(type) from:")
try printJson(data)
}
do {
var data: Data
data = try encode(Value1(id: 1, fullName: "Name", addressLine1: "Address"))
try decode(Value1.self, from: data)
data = try encode(Value2(id: 2, fullName: "Name", addressLine_1: "Address"))
_ = try decode(Value1.self, from: data)
_ = try decode(Value2.self, from: data)
} catch {
print("❌ Failed with error:", error)
}
Output:
✅ Decoded Value1 from:
{
"address_line1" : "Address",
"full_name" : "Name",
"id" : 1
}
✅ Decoded Value1 from:
{
"address_line_1" : "Address",
"full_name" : "Name",
"id" : 2
}
❌ Failed with error: keyNotFound(CodingKeys(stringValue: "addressLine_1", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"addressLine_1\", intValue: nil) (\"addressLine_1\"), with divergent representation addressLine1, converted to address_line_1.", underlyingError: nil))
convertFromSnakeCase works correctly and you can can check it in first decode:
_ = try decode(Value1.self, from: data)
After that, when you try to decode the same data but with Value2 type it surely fails as it expects different property name. This is your encoded snake case JSON:
{
"address_line_1" : "Address",
"full_name" : "Name",
"id" : 2
}
After decoder conversion address_line_1 becomes addressLine1 (the same applies to full_name) which fits properties of Value1. If you try to decode the same data for Value2 it fails as property name requires addressLine_1.
In your case, optimal strategy would be to use custom coding keys, like this:
struct Value2: Codable {
private enum Value2CodingKey: String, CodingKey {
case id
case fullName = "full_name"
case addressLine1 = "address_line_1"
}
let id: Int
let fullName: String
let addressLine1: String
}
I found a solution without using custom coding keys, but custom coding strategy instead, so coders handle _ before numbers as well.
So that addressLine1 encodes to address_line_1, and address_line_1 decodes to addressLine1
Usage:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCaseWithNumbers
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCaseWithNumbers
Coder implementation:
extension JSONEncoder.KeyEncodingStrategy {
static var convertToSnakeCaseWithNumbers: JSONEncoder.KeyEncodingStrategy {
.custom { codingKeys -> CodingKey in
let stringValue = codingKeys.last!.stringValue
let newKey = AnyKey(stringValue: convertToSnakeCase(stringValue))!
return newKey
}
}
private static func convertToSnakeCase(_ stringKey: String) -> String {
var key = stringKey
let searchRange = key.index(after: key.startIndex)..<key.endIndex
let nsRange = key.nsRange(from: searchRange)
let matches = NSRegularExpression("([A-Z])|([0-9]+)").matches(in: key, options: [], range: nsRange)
for match in matches.reversed() {
guard let range = key.range(from: match.range) else { continue }
key.insert("_", at: range.lowerBound)
}
return key.lowercased()
}
}
extension JSONDecoder.KeyDecodingStrategy {
static var convertFromSnakeCaseWithNumbers: JSONDecoder.KeyDecodingStrategy {
.custom { (codingKeys) -> CodingKey in
let stringValue = codingKeys.last!.stringValue
let newKey = AnyKey(stringValue: convertFromSnakeCase(stringValue))!
return newKey
}
}
private static func convertFromSnakeCase(_ stringKey: String) -> String {
guard stringKey.contains("_") else {
return stringKey
}
let components = stringKey.split(separator: "_").map({ $0.firstCapitalized })
return components.joined().firstLowercased
}
}
private extension NSRegularExpression {
convenience init(_ pattern: String) {
do {
try self.init(pattern: pattern)
} catch {
preconditionFailure("Illegal regular expression: \(pattern).")
}
}
}
private extension StringProtocol {
var firstLowercased: String { prefix(1).lowercased() + dropFirst() }
var firstCapitalized: String { prefix(1).capitalized + dropFirst() }
}
enum AnyKey: CodingKey {
case string(String)
case int(Int)
var stringValue: String {
switch self {
case .string(let string):
return string
case .int(let int):
return "\(int)"
}
}
var intValue: Int? {
guard case let .int(int) = self else { return nil }
return int
}
init?(stringValue: String) {
guard !stringValue.isEmpty else { return nil }
self = .string(stringValue)
}
init?(intValue: Int) {
self = .int(intValue)
}
}

How to decode single unusual property among many Decodable Swift?

I have a struct conforming to Decodable. It has 50 String properties and only one Bool. That bool is coming from server like a string "false"/"true" or sometimes like as integer 0/1, so cannot be decoded from the box. How can I make it decoding but not write huge amount of manual decoding of all that 50 String properties? Maybe somehow override decodeIfPresent for Bool, but I could not make it working.
How I can avoid creating init with decoding everything and all that stuff manually and only handle that one Bool? Without computed property if possible.
struct Response: Decodable {
var s1: String
var s2: String
var s3: String
//...........
var s50: String
var b1: Bool
var b2: Bool
}
Here is json example:
{
"s1":"string"
"s2":"string"
"s3":"string"
//..........
"s50":"string"
"b1":"true"
"b2":"0"
}
Tried this but does not work(((
extension KeyedDecodingContainer { //Doesn't work, no execution
func decodeIfPresent(_ type: Bool.Type, forKey key: K) throws -> Bool {
return try! self.decodeIfPresent(Bool.self, forKey: key)
}
func decodeIfPresent(_ type: Bool.Type, forKey key: K) throws -> Bool? {
return try? self.decodeIfPresent(Bool.self, forKey: key)
}
}
One way to solve this is to write a property wrapper that handles the custom decoding, like this:
#propertyWrapper
struct FunnyBool: Decodable {
var wrappedValue: Bool
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let value = try? container.decode(Bool.self) {
wrappedValue = value
}
else if
let string = try? container.decode(String.self),
let value = Bool(string)
{
wrappedValue = value
}
else if
let int = try? container.decode(Int.self),
let value = int == 0 ? false : int == 1 ? true : nil
{
wrappedValue = value
}
else {
// Default...
wrappedValue = false
}
}
}
Use it like this:
struct Response: Decodable {
var s1: String
var s2: String
#FunnyBool var b: Bool
}
let json = #"{ "s1": "hello", "s2": "world", "b": 1 }"#
let response = try! JSONDecoder().decode(Response.self, from: json.data(using: .utf8)!)
dump(response)
Playground output:
▿ __lldb_expr_11.Response
- s1: "hello"
- s2: "world"
▿ _b: __lldb_expr_11.FunnyBool
- wrappedValue: true

Cannot convert value of type 'User' to expected argument type '[String : Any]' [duplicate]

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

How to deal with completely dynamic JSON responses

Maybe someone in the community has had similar struggles and have come up with a workable solution.
We're currently working on a polyglot key/value store. Given this, we'll generally have no knowledge of what will be stored ahead of time.
Consider the following struct
struct Character : Codable, Equatable {
let name: String
let age: Int
let gender: Gender
let hobbies: [String]
static func ==(lhs: Character, rhs: Character) -> Bool {
return (lhs.name == rhs.name
&& lhs.age == rhs.age
&& lhs.gender == rhs.gender
&& lhs.hobbies == rhs.hobbies)
}
}
When sending/receiving Character entities over the wire, everything is fairly straight forward. The user can provide us the Type in which we can decode into.
However, we do have the ability to dynamically query the entities stored within the backend. For example, we can request the value of the 'name' property and have that returned.
This dynamism is a pain point. In addition to not knowing the type of the properties outside of the fact that they are Codable, the format that is returned can be dynamic as well.
Here's some examples of response for two different calls extracting properties:
{"value":"Bilbo"}
and
{"value":["[Ljava.lang.Object;",["Bilbo",111]]}
In some cases, it could be an equivalent of a dictionary.
Right now, I have the following structs for dealing with responses:
fileprivate struct ScalarValue<T: Decodable> : Decodable {
var value: T?
}
Using the Character example, the type passed to the decoder would be:
ScalarValue<Character>.self
However, for the single value, array, or dictionary case, I'm somewhat stuck.
I've started with something like:
fileprivate struct AnyDecodable: Decodable {
init(from decoder: Decoder) throws {
// ???
}
}
Based on the possible return types I've described above, I'm not sure if this is possible with the current API.
Thoughts?
Swift can definitely handle an arbitrary JSON decodable. This isn't the same thing as an arbitrary decodable. JSON can't encode all possible values. But this structure will decode anything that can be expressed in JSON, and from there you can explore it in a type-safe way without resorting to dangerous and awkward tools like Any.
enum JSON: Decodable, CustomStringConvertible {
var description: String {
switch self {
case .string(let string): return "\"\(string)\""
case .number(let double):
if let int = Int(exactly: double) {
return "\(int)"
} else {
return "\(double)"
}
case .object(let object):
return "\(object)"
case .array(let array):
return "\(array)"
case .bool(let bool):
return "\(bool)"
case .null:
return "null"
}
}
var isEmpty: Bool {
switch self {
case .string(let string): return string.isEmpty
case .object(let object): return object.isEmpty
case .array(let array): return array.isEmpty
case .null: return true
case .number, .bool: return false
}
}
struct Key: CodingKey, Hashable, CustomStringConvertible {
var description: String {
return stringValue
}
var hashValue: Int { return stringValue.hash }
static func ==(lhs: JSON.Key, rhs: JSON.Key) -> Bool {
return lhs.stringValue == rhs.stringValue
}
let stringValue: String
init(_ string: String) { self.stringValue = string }
init?(stringValue: String) { self.init(stringValue) }
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
case string(String)
case number(Double) // FIXME: Split Int and Double
case object([Key: JSON])
case array([JSON])
case bool(Bool)
case null
init(from decoder: Decoder) throws {
if let string = try? decoder.singleValueContainer().decode(String.self) { self = .string(string) }
else if let number = try? decoder.singleValueContainer().decode(Double.self) { self = .number(number) }
else if let object = try? decoder.container(keyedBy: Key.self) {
var result: [Key: JSON] = [:]
for key in object.allKeys {
result[key] = (try? object.decode(JSON.self, forKey: key)) ?? .null
}
self = .object(result)
}
else if var array = try? decoder.unkeyedContainer() {
var result: [JSON] = []
for _ in 0..<(array.count ?? 0) {
result.append(try array.decode(JSON.self))
}
self = .array(result)
}
else if let bool = try? decoder.singleValueContainer().decode(Bool.self) { self = .bool(bool) }
else {
self = .null
}
}
var objectValue: [String: JSON]? {
switch self {
case .object(let object):
let mapped: [String: JSON] = Dictionary(uniqueKeysWithValues:
object.map { (key, value) in (key.stringValue, value) })
return mapped
default: return nil
}
}
var arrayValue: [JSON]? {
switch self {
case .array(let array): return array
default: return nil
}
}
subscript(key: String) -> JSON? {
guard let jsonKey = Key(stringValue: key),
case .object(let object) = self,
let value = object[jsonKey]
else { return nil }
return value
}
var stringValue: String? {
switch self {
case .string(let string): return string
default: return nil
}
}
var doubleValue: Double? {
switch self {
case .number(let number): return number
default: return nil
}
}
var intValue: Int? {
switch self {
case .number(let number): return Int(number)
default: return nil
}
}
subscript(index: Int) -> JSON? {
switch self {
case .array(let array): return array[index]
default: return nil
}
}
var boolValue: Bool? {
switch self {
case .bool(let bool): return bool
default: return nil
}
}
}
With this, you can do things like:
let bilboJSON = """
{"value":"Bilbo"}
""".data(using: .utf8)!
let bilbo = try! JSONDecoder().decode(JSON.self, from: bilboJSON)
bilbo["value"] // "Bilbo"
let javaJSON = """
{"value":["[Ljava.lang.Object;",["Bilbo",111]]}
""".data(using: .utf8)!
let java = try! JSONDecoder().decode(JSON.self, from: javaJSON)
java["value"]?[1] // ["Bilbo", 111]
java["value"]?[1]?[0]?.stringValue // "Bilbo" (as a String rather than a JSON.string)
The proliferation of ? is somewhat ugly, but using throws on this doesn't really make the interface much nicer in my experiments (particularly because subscripts can't throw). Some tweaking may be advisable based on your particular use cases.
I wrote an AnyCodable struct myself for this purpose:
struct AnyCodable: Decodable {
var value: Any
struct CodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
init?(stringValue: String) { self.stringValue = stringValue }
}
init(value: Any) {
self.value = value
}
init(from decoder: Decoder) throws {
if let container = try? decoder.container(keyedBy: CodingKeys.self) {
var result = [String: Any]()
try container.allKeys.forEach { (key) throws in
result[key.stringValue] = try container.decode(AnyCodable.self, forKey: key).value
}
value = result
} else if var container = try? decoder.unkeyedContainer() {
var result = [Any]()
while !container.isAtEnd {
result.append(try container.decode(AnyCodable.self).value)
}
value = result
} else if let container = try? decoder.singleValueContainer() {
if let intVal = try? container.decode(Int.self) {
value = intVal
} else if let doubleVal = try? container.decode(Double.self) {
value = doubleVal
} else if let boolVal = try? container.decode(Bool.self) {
value = boolVal
} else if let stringVal = try? container.decode(String.self) {
value = stringVal
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "the container contains nothing serialisable")
}
} else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not serialise"))
}
}
}
extension AnyCodable: Encodable {
func encode(to encoder: Encoder) throws {
if let array = value as? [Any] {
var container = encoder.unkeyedContainer()
for value in array {
let decodable = AnyCodable(value: value)
try container.encode(decodable)
}
} else if let dictionary = value as? [String: Any] {
var container = encoder.container(keyedBy: CodingKeys.self)
for (key, value) in dictionary {
let codingKey = CodingKeys(stringValue: key)!
let decodable = AnyCodable(value: value)
try container.encode(decodable, forKey: codingKey)
}
} else {
var container = encoder.singleValueContainer()
if let intVal = value as? Int {
try container.encode(intVal)
} else if let doubleVal = value as? Double {
try container.encode(doubleVal)
} else if let boolVal = value as? Bool {
try container.encode(boolVal)
} else if let stringVal = value as? String {
try container.encode(stringVal)
} else {
throw EncodingError.invalidValue(value, EncodingError.Context.init(codingPath: [], debugDescription: "The value is not encodable"))
}
}
}
}
It works with nested dictionaries/arrays too. You can try it with any json in a playground.
let decoded = try! JSONDecoder().decode(AnyCodable.self, from: jsonData)
Yes, is possible to achieve what you described via the existing Codable API, and in an elegant manner I'd say (though I might be subjective here since I'm talking about my code :) ).
Let try to figure out what is needed for this task:
First things first, you need to declare all properties as optional. This is needed as the decoder is likely to have to deal with partial responses.
struct Character: Codable {
let name: String?
let age: Int?
let hobbies: [String]?
}
Next, we need a way to figure out how to map the struct properties to the various fields from the partial JSONs. Luckily the Codable API can help us here via the CodingKeys enum:
enum CodingKeys: String, CodingKey {
case name
case age
case hobbies
}
The first tricky part is to somehow convert the CodingKeys enum into an array of strings, that we can use for the array response - {"value":["[Ljava.lang.Object;",["Bilbo",111]]}. We are in luck here, there are various sources on the internet and SO that address the question of getting all cases of an enum. My preferred solutions is the RawRepresentable extension, since CodingKey is raw representable and it's raw value is a String:
// Adds support for retrieving all enum cases. Since we refer a protocol here,
// theoretically this method can be called on other types than enum
public extension RawRepresentable {
static var enumCases: [Self] {
var caseIndex: Int = 0
return Array(AnyIterator {
defer { caseIndex += 1 }
return withUnsafePointer(to: &caseIndex) {
$0.withMemoryRebound(to: Self.self, capacity: 1) { $0.pointee }
}
})
}
}
We're almost there, but we need some more work before we can decode.
Now that we have a Decodable type, a list of coding keys to use, we need a decoder that makes use of these. But before that, we need to be able to recognise types that can be partially decoded. Let's add a new protocol
protocol PartiallyDecodable: Decodable {
associatedtype PartialKeys: RawRepresentable
}
and make Character conform to it
struct Character : Codable, PartiallyDecodable {
typealias PartialKeys = CodingKeys
The finishing piece is the decoding part. We can reuse the JSONDecoder that comes with the standard library:
// Tells the form of data the server sent and we want to decode:
enum PartialDecodingStrategy {
case singleKey(String)
case arrayOfValues
case dictionary
}
extension JSONDecoder {
// Decodes an object by using a decoding strategy
func partialDecode<T>(_ type: T.Type, withStrategy strategy: PartialDecodingStrategy, from data: Data) throws -> T where T : PartiallyDecodable, T.PartialKeys.RawValue == String {
Connecting all of the above results in the following infrastructure:
// Adds support for retrieving all enum cases. Since we refer a protocol here,
// theoretically this method can be called on other types than enum
public extension RawRepresentable {
static var enumCases: [Self] {
var caseIndex: Int = 0
return Array(AnyIterator {
defer { caseIndex += 1 }
return withUnsafePointer(to: &caseIndex) {
$0.withMemoryRebound(to: Self.self, capacity: 1) { $0.pointee }
}
})
}
}
protocol PartiallyDecodable: Decodable {
associatedtype PartialKeys: RawRepresentable
}
// Tells the form of data the server sent and we want to decode:
enum PartialDecodingStrategy {
case singleKey(String)
case arrayOfValues
case dictionary
}
extension JSONDecoder {
// Decodes an object by using a decoding strategy
func partialDecode<T>(_ type: T.Type, withStrategy strategy: PartialDecodingStrategy, from data: Data) throws -> T where T : PartiallyDecodable, T.PartialKeys.RawValue == String {
guard let partialJSON = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [AnyHashable:Any] else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Invalid JSON"))
}
guard let value = partialJSON["value"] else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Missing \"value\" key"))
}
let processedJSON: [AnyHashable:Any]
switch strategy {
case let .singleKey(key):
processedJSON = [key:value]
case .arrayOfValues:
guard let values = value as? [Any],
values.count == 2,
let properties = values[1] as? [Any] else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Invalid JSON: expected a 2 elements array for the \"value\" key"))
}
processedJSON = zip(T.PartialKeys.enumCases, properties)
.reduce(into: [:]) { $0[$1.0.rawValue] = $1.1 }
case .dictionary:
guard let dict = value as? [AnyHashable:Any] else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Invalid JSON: expected a dictionary for the \"value\" key"))
}
processedJSON = dict
}
return try decode(type, from: JSONSerialization.data(withJSONObject: processedJSON, options: []))
}
}
We want to be able to partially decode Character, so we make it adopt all the required protocols:
struct Character: Codable, PartiallyDecodable {
typealias PartialKeys = CodingKeys
let name: String?
let age: Int?
let hobbies: [String]?
enum CodingKeys: String, CodingKey {
case name
case age
case hobbies
}
}
Now the fun part, let's test it:
let decoder = JSONDecoder()
let jsonData1 = "{\"value\":\"Bilbo\"}".data(using: .utf8)!
print((try? decoder.partialDecode(Character.self,
withStrategy: .singleKey(Character.CodingKeys.name.rawValue),
from: jsonData1)) as Any)
let jsonData2 = "{\"value\":[\"[Ljava.lang.Object;\",[\"Bilbo\",111]]}".data(using: .utf8)!
print((try? decoder.partialDecode(Character.self,
withStrategy: .arrayOfValues,
from: jsonData2)) as Any)
let jsonData3 = "{\"value\":{\"name\":\"Bilbo\",\"age\":111,\"hobbies\":[\"rings\"]}}".data(using: .utf8)!
print((try? decoder.partialDecode(Character.self,
withStrategy: .dictionary,
from: jsonData3)) as Any)
As we might expect, the output is the following:
Optional(MyApp.Character(name: Optional("Bilbo"), age: nil, hobbies: nil))
Optional(MyApp.Character(name: Optional("Bilbo"), age: Optional(111), hobbies: nil))
Optional(MyApp.Character(name: Optional("Bilbo"), age: Optional(111), hobbies: Optional(["rings"])))
As we can see, with the proper infrastructure laid out, the only requirements for a type to be partially decodable is to conform to PartiallyDecodable and to have an enum that says which keys to decode. These requirements are easy to be followed.

Can Swift 4's JSONDecoder be used with Firebase Realtime Database?

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