I want to encode Dictionary to json with JSONEncoder.
It seems like a Request, receive a dictionary as parameter and encode it to json as http body.
The code is looks like this:
let dict = ["name": "abcde"]
protocol Request {
var params: [String: Encodable] { get set }
func encode<T>(_ value: T) throws -> Data where T : Encodable
}
extension Request {
func encode<T>(_ value: T) throws -> Data where T : Encodable {
return try JSONEncoder().encode(value)
}
var body: Data? {
if let encoded = try? self.encode(self.params) {
return encoded
}
return nil
}
}
struct BaseRequest: Request {
var params: [String : Encodable]
}
let req = BaseRequest(params: dict)
let body = req.body
But this code occurs error
Fatal error: Dictionary<String, Encodable> does not conform to Encodable because Encodable does not conform to itself. You must use a concrete type to encode or decode.
How could I make this encodable?
You have to introduce type erasure as follows:
struct AnyEncodable: Encodable {
let value: Encodable
init(value: Encodable) {
self.value = value
}
func encode(to encoder: Encoder) throws {
try value.encode(to: encoder)
}
}
struct Model: Encodable {
var params: [String: AnyEncodable]
}
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let json = try! encoder.encode(
Model(
params: [
"hello" : AnyEncodable.init(value: "world")
]
).params
)
print(String(data: json, encoding: .utf8))
If you want to define your struct as conforming to Codable, you can do it like this:
struct Model: Codable {
var param1: String
var param2: Int
}
let model = Model(param1: "test", param2: 0)
let encoded = try? JSONEncoder().encode(model)
let decoded = try? JSONDecoder().decode(Model.self, from: encoded!)
It won't really work if you set params: [String: Any] because the encoders/decoders don't know how to encode/decode Any, but they can do it for the primitive types.
If you want more help, you should read more about the new Codable protocol. I recommend this: https://hackernoon.com/everything-about-codable-in-swift-4-97d0e18a2999
Related
I am using the following struct that conforms to Codable to represent my data within a Set, I then take this set encode it into a JSON Array that looks like the following [{"myexample":"Example5","id":3}]
Struct and Encoding:
struct Model : Codable, Hashable {
var myexample: String?
var id: Int?
}
var mySet: Set<Model> = []
mySet.insert(Model(myexample: "Example4", id: 3))
do {
let json = try JSONEncoder().encode(mySet)
print(String(data: json, encoding: .utf8)!)
} catch {
print(error)
}
How can I inversely decode this array using the following function?
Currently getting this error:
Value of type 'Set' has no member 'utf8'
func processArray(array:Set<Model> = []) {
do{
let mydata = Data(array.utf8.data)
let decoded = try JSONDecoder().decode([Model].self,from: mydata)
print(decoded)
}catch let jsonErr {
print(jsonErr)
}
}
JSON is a string format so a JSON array is also string
func processJSON(_ json: String) {
do{
let mydata = Data(json.utf8)
let decoded = try JSONDecoder().decode(Set<Model>.self,from: mydata)
print(decoded)
} catch {
print(error)
}
}
What's the best way to pass a custom Codable Model into a firebase function? Here's what I have right now, but I get the error: Unsupported type: __SwiftValue for value
struct ExampleStruct: Codable {
var hi: String
var world: Int
var last: [String]
}
let myModel = ExampleStruct(hi: "World", world: 1, last: ["hi world"])
functions.httpsCallable(FirebaseFunctionsNames.exampleName).call(["data":myModel]) { [weak self] (result, error) in
print(result)
print(error)
}
The following ended up solving my question (note: I wouldn't recommend the force unwraps and casts but it's easier to consume in StackOverflow this way :)
struct ExampleStruct: Codable {
var hi: String
var world: Int
var last: [String]
}
let myModel = ExampleStruct(hi: "World", world: 1, last: ["hi world"])
func hiWorld(input: myModel) {
let jsonEncoder = JSONEncoder()
let jsonData = try! jsonEncoder.encode(input)
let json = String(data: jsonData, encoding: .utf8)!
functions.httpsCallable(FirebaseFunctionsNames.exampleName).call(["data":json]) { [weak self] (result, error) in
print(result)
print(error)
}
}
And then on the backend (Typescript):
const myModel = JSON.parse(data.data) as ExampleStruct;
Is there a generic way to serialize/deserialize objects for iOS? I was using the following code, and the system functions I was calling were deprecated in iOS 12:
func object(forKey:String) -> Any? {
if let data = get(BLOB_COL, forKey) {
return NSKeyedUnarchiver.unarchiveObject(with: data as! Data)
}
return nil
}
func set(_ object:Any, forKey: String) {
let data = NSKeyedArchiver.archivedData(withRootObject: object)
updateOrInsert(forKey, BLOB_COL, data)
}
It looks like the new versions of these functions requires knowledge of the object classes, and different signatures for unarchiving different collection types... is there a simple way to handle this generically?
If you would like to use NSKeyedArchiver/NSKeyedUnarchiver:
When archiving change:
NSKeyedArchiver.archivedData...
to
NSKeyedArchiver.archivedData(withRootObject: object, requiringSecureCoding: false)
And when unarchiving change
NSKeyedUnarchiver.unarchiveObject...
to
NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
Codable protocol generic approach
If you would like to use Codable to encode and decode (serialize) your Data you can extend Encodable protocol and create a method to encode any object that conforms to it:
extension Encodable {
func data(using encoder: JSONEncoder = JSONEncoder()) throws -> Data { try encoder.encode(self) }
}
And to decode the encoded data you can extend Data and create a generic method that decode any object that conforms to Decodable:
extension Data {
func object<T: Decodable>(using decoder: JSONDecoder = JSONDecoder()) throws -> T {
try decoder.decode(T.self, from: self)
}
// you can also create a string property to convert the JSON data to String
var string: String? { String(data: self, encoding: .utf8) }
}
Usage:
struct Person: Codable {
let name: String
let age: Int
}
do {
// encoding
let person = Person(name: "Joe", age: 10)
var people = [Person]()
people.append(person)
let data = try people.data()
print(data.string ?? "") // [{"name":"Joe","age":10}]
// decoding
let loadedPeople: [Person] = try data.object()
loadedPeople.forEach({print( $0.name, $0.age)}) // Joe 10
} catch {
print(error)
}
As Leo says, stop using NSKeyedArchiver and use the Codable protocol. That's the modern, Swifty way to serialize/deserialize your objects.
I want to have a Struct that can be instantiated via normal Codable protocol or a Dictionary (Existing code requires the Dictionary instantiation).
I have this code in a playground, but I'm not sure what to do in my 2nd init that takes a Dictionary. How do I make a Decoder object from a dictionary?
import Foundation
public protocol Parsable: Decodable {
init(dict: [String: Any]) throws
}
struct LinkModel: Parsable {
var href: String
var text: String
init(dict: [String: Any]) throws {
href = "/store/options.aspx"
text = "Buy"
}
}
struct ResponseModel: Parsable {
var link: LinkModel?
let showCell: Bool
enum CodingKeys : String, CodingKey {
case link
case showCell = "show"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let linkResponses = try container.decode([LinkModel].self, forKey: .link)
link = linkResponses.first
showCell = try container.decode(Bool.self, forKey: .showCell)
}
init(dict: [String: Any]) throws {
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: [])
let decoder = ??? // what do I do here?
self.init(from: decoder)
}
}
let jsonText = """
{
"show": true,
"link": [
{
"text": "Buy",
"href": "/store/options.aspx"
}
]
}
"""
// test standard Decodable instantiation
let jsonData = jsonText.data(using: .utf8)!
let model = try! JSONDecoder().decode(ResponseModel.self, from: jsonData)
print(model.link?.href)
// test dictionary instantiation
...
Extend your Parsable protocol to automatically generate the initializer you're looking for.
extension Parsable {
init(dict: [String: Any]) throws {
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: [])
let decoder = JSONDecoder()
self = try decoder.decode(Self.self, from: jsonData)
}
}
You're on the right path.
import Foundation
public protocol Parsable: Decodable {
init(dict: [String: Any]) throws
}
struct LinkModel: Parsable {
var href: String
var text: String
init(dict: [String: Any]) throws {
href = "/store/options.aspx"
text = "Buy"
}
}
struct ResponseModel: Parsable {
var link: LinkModel?
let showCell: Bool
enum CodingKeys : String, CodingKey {
case link
case showCell = "show"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let linkResponses = try container.decode([LinkModel].self, forKey: .link)
link = linkResponses.first
showCell = try container.decode(Bool.self, forKey: .showCell)
}
init(dict: [String: Any]) throws {
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: [])
// 1.
let decoder = JSONDecoder()
// 2.
let result = try decoder.decode(ResponseModel.self, from: jsonData)
// 3.
self = result
}
}
let jsonText = """
{
"show": true,
"link": [
{
"text": "Buy",
"href": "/store/options.aspx"
}
]
}
"""
// test standard Decodable instantiation
let jsonData = jsonText.data(using: .utf8)!
let model = try! JSONDecoder().decode(ResponseModel.self, from: jsonData)
print(model.link?.href)
All I did was:
create a JSONdecoder object.
use that JSONdecoder to decode an object of type ResponseModel
assign the result of the decoding to self. This way all properties of self are assigned.
I am trying to extend Dictionary with the following code:
extension Dictionary where Key: ExpressibleByStringLiteral, Value: AnyObject {
var jsonString: String? {
if let dict = (self as AnyObject) as? Dictionary<String, AnyObject> {
do {
let data = try JSONSerialization.data(withJSONObject: dict, options: JSONSerialization.WritingOptions(rawValue: UInt.allZeros))
if let string = String(data: data, encoding: String.Encoding.utf8) {
return string
}
} catch {
print(error)
}
}
return nil
}
}
Where I write something like:
let x: [String: String] = ["": ""]
x.jsonString
I get this error:
Value of type '[String: String]' as no member 'jsonString'
Anything I am missing?
There is no need to constrain Dictionary Value type at all:
extension Dictionary where Key: ExpressibleByStringLiteral {
var jsonString: String? {
guard let data = try? JSONSerialization.data(withJSONObject: self)
else { return nil }
return String(data: data, encoding: .utf8)
}
}
Since String is a value type , look for it's
public struct String {
and AnyObject refers to any instance of a class only , and is equivalent to id in Objective-C , so this declaration of x
[String: String] doesn't fit with [String: AnyObject]
because Any refers to any instance of a class, struct, or enum so it'll fit perfectly