I'm trying to create a generic method like the following:
private func map<T>(type:T, jsonString:String) -> T
{
do
{
let model = try Mapper<type>().map(JSONString: jsonString)!
return model
}
catch
{
Log.error("Failed to convert JSON jsonString to model object: \(jsonString)")
}
return EmptyModel()
}
but it result in compile error:
Error: use of undeclared type 'type'
How can I change it to use the specified type (a class object) with the Mapper's generic value?
You can use T instead of type:
let model = try Mapper<T>().map(JSONString: jsonString)!
You might want to change method signature, so it returns an instance of T and not the type T itself:
private func map<T>(type: T.Type, jsonString: String) -> T
That being said, Swift already has its JSONDecoder. It might already support what you are trying to implement.
let decoder = JSONDecoder()
let model = try decoder.decode(Model.self, from: data)
Related
I'm using the SocketIO library to connect my iOS app to my server.
I want to emit some data to the server and get a json dictionary back in the acknowledgment. I currently have something like this:
SocketHandler.mySocket.emitWithAck("my_event", [session, someInput]).timingOut(after: 3) {data in
let myData = try? JSONDecoder().decode(myStruct.self, from: data)
MyStruct is defined as Class inheriting from Decodable and resembles the structure of the json I expect.
I get the following error: Cannot convert value of type 'Any' to expected argument type 'Data'
Any idea how I can tackle that type casting? Or would I need to go a totally other route?
(Swift 4.1 for iOS 11.3)
Cheers!
If anyone else is wondering how to use SocketIO with Decodable, I created a little extension for the client to accept Decodable in the callback, based on Dan Karbayev's answer.
import Foundation
import SocketIO
extension Decodable {
init(from any: Any) throws {
let data = try JSONSerialization.data(withJSONObject: any)
self = try JSONDecoder().decode(Self.self, from: data)
}
}
extension SocketIOClient {
func on<T: Decodable>(_ event: String, callback: #escaping (T)-> Void) {
self.on(event) { (data, _) in
guard !data.isEmpty else {
print("[SocketIO] \(event) data empty")
return
}
guard let decoded = try? T(from: data[0]) else {
print("[SocketIO] \(event) data \(data) cannot be decoded to \(T.self)")
return
}
callback(decoded)
}
}
}
Usage:
socket.on("location") { (data: LocationEventData) in
// ...
}
socket.on("success") { (name: String) in
// ...
}
Where LocationEventData and String are Decodable.
There're two things:
decode(_:from:) accepts a Data as a second parameter. To be able to decode from Any you'll need to add an extension to first serialize the data and then pass it to JSONDecoder, like this:
extension Decodable {
init(from any: Any) throws {
let data = try JSONSerialization.data(withJSONObject: any)
self = try JSONDecoder().decode(Self.self, from: data)
}
}
AckCallback's parameter is of an array type (i.e. [Any]), so you should get the first element of that array.
To make sure that you have indeed a decodable data (a dictionary or a JSON object) you can write something like this:
SocketHandler.mySocket.emitWithAck("my_event", [session, someInput]).timingOut(after: 3) { data in
guard let dict = data.first as? [String: Any] else { return }
let myData = try? myStruct(from: dict)
// ...
}
I´m developing a project in Swift 3 using ObjectMapper and I have a lot of functions using the same code.
The function which does the conversion is this:
func convertCategories (response:[[String : Any]]) {
let jsonResponse = Mapper<Category>().mapArray(JSONArray: response )
for item in jsonResponse!{
print(item)
let realm = try! Realm()
try! realm.write {
realm.add(item)
}
}
}
And I want to pass Category (Mapper) as an argument, so I can pass any type of Class Types to the function and use only one function to do the job, it would look like this:
func convertObjects (response:[[String : Any]], type: Type) {
let jsonResponse = Mapper<Type>().mapArray(JSONArray: response )
...
I´ve tried a lot of thinks but without result, ¿Can anyone help me achieving this?
Edited: For all with the same problem, the solution is this:
func convertObjects <Type: BaseMappable> (response:[[String : Any]], type: Type)
{
let jsonResponse = Mapper<Type>().mapArray(JSONArray: response )
for item in jsonResponse!{
print(item)
let realm = try! Realm()
try! realm.write {
realm.add(item as! Object)
}
}
}
And to call the function is:
self.convertObjects(response: json["response"] as! [[String : Any]], type: type)
I suspect you're just having a syntax issue here. What you mean is something like:
func convertObjects<Type: BaseMappable>(response:[[String : Any]], type: Type)
You can also write it this way (which is sometimes more readable, particularly when things get complicated):
func convertObjects<Type>(response:[[String : Any]], type: Type)
where Type: BaseMappable {
You'd typically call it as:
convertObjects(response: response, type: Category.self)
The point is that convertObjects needs to be specialized over each type you want to convert, and that requires declaring a type parameter (<Type>).
here is my issue, I would like to create a function with this prototype :
func doPostRequest(......)->JSON()
And I write it like that :
func downloadData(completed:#escaping()->()){
Alamofire.request(url).responseJSON(completionHandler: {
response in
let result = response.result
if let dict = ... {
self._temp = String(format: "%.0f °C", temp - 273.15)
...
}
completed()
})
}
I'd like to return an Any object or dictionary, something with my JSON in... but each time I try to implement return I have a nil object ! Maybe a scope problem how can I implement this function to have
var myJson:NSDictionary
myJson=downloadData(......) ???
Thanks for your help
Since the method in the body works asynchronously you have to declare your request method with an completion handler for example
func doPostRequest(completion: #escaping ([String:Any])->())
On return it passes a Swift dictionary.
The method can be used with this code:
var myJson = [String:Any]()
...
doPostRequest() { json in
self.myJson = json
// do something with the returned data
}
first you need to create a ObjectMapper to map your objects and use AlamofireObjectMapper to get
try this code
request(url, method: .post, parameters:params).validate().responseObject{(response:
DataResponse<objectMapperclass>)in
switch response.result{
case.success(let data):
let objects = data
case.faliure(_):
}
}
Right now I can inspect variables of an object using Mirror type. But can I set values for my variables using mirroring? Or maybe there's another pure-Swift way?
For example, I'd like to create an object (a Swift struct) from JSON. Is it possible without subclassing NSObject and using Objective-C functions for that?
This was the best I can do at the moment. It is still missing converting the mirrorObject back to its generic type. FYI this is using SwiftyJSON
func convertToObject<T>(json: JSON, genericObject: T) -> T {
let mirroredObject = Mirror(reflecting: genericObject)
for (_, var attr) in mirroredObject.children.enumerate() {
if let propertyName = attr.label as String! {
attr.value = json[propertyName]
print(propertyName)
print(attr.value)
}
}
// Figure out how to convert back to object type...
}
This is an old question, but the answer was not working for me.
I had to change my swift object to a NSObject to make things work, and also to have dynamic properties.
In my case I use the pod Marshal to deserialize Data.
class MyClass: NSObject, Unmarshaling
{
// #objc dynamic make property available for NSObject
#objc dynamic var myProperty: String?
required init(object: MarshaledObject) throws {
super.init()
initUsingReflection(object: object)
}
func initUsingReflection(object: MarshaledObject) {
let mirror = Mirror(reflecting: self)
// we go through children
for child in mirror.children {
guard let key = child.label else {
continue
}
// This line is here to get the value from json, in my case I already know the type I needed
let myValue: String = try! object.value(for: key)
// The trick is here, setValue only exist in NSObject and not in swift object.
self.setValue(myValue, forKey: key)
}
}
}
I'm looking for a way to automatically serialize and deserialize class instances in Swift. Let's assume we have defined the following class …
class Person {
let firstName: String
let lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}
… and Person instance:
let person = Person(firstName: "John", lastName: "Doe")
The JSON representation of person would be the following:
{
"firstName": "John",
"lastName": "Doe"
}
Now, here are my questions:
How can I serialize the person instance and get the above JSON without having to manually add all properties of the class to a dictionary which gets turned into JSON?
How can I deserialize the above JSON and get back an instantiated object that is statically typed to be of type Person? Again, I don't want to map the properties manually.
Here's how you'd do that in C# using Json.NET:
var person = new Person("John", "Doe");
string json = JsonConvert.SerializeObject(person);
// {"firstName":"John","lastName":"Doe"}
Person deserializedPerson = JsonConvert.DeserializeObject<Person>(json);
As shown in WWDC2017 # 24:48 (Swift 4), we will be able to use the Codable protocol. Example
public struct Person : Codable {
public let firstName:String
public let lastName:String
public let location:Location
}
To serialize
let payload: Data = try JSONEncoder().encode(person)
To deserialize
let anotherPerson = try JSONDecoder().decode(Person.self, from: payload)
Note that all properties must conform to the Codable protocol.
An alternative can be JSONCodable which is used by Swagger's code generator.
You could use EVReflection for that. You can use code like:
var person:Person = Person(json:jsonString)
or
var jsonString:String = person.toJsonString()
See the GitHub page for more detailed sample code. You only have to make EVObject the base class of your data objects. No mapping is needed (as long as the json keys are the same as the property names)
Update: Swift 4 has support for Codable which makes it almost as easy as EVReflection but with better performance. If you do want to use an easy contractor like above, then you could use this extension: Stuff/Codable
With Swift 4, you simply have to make your class conform to Codable (Encodable and Decodable protocols) in order to be able to perform JSON serialization and deserialization.
import Foundation
class Person: Codable {
let firstName: String
let lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}
Usage #1 (encode a Person instance into a JSON string):
let person = Person(firstName: "John", lastName: "Doe")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted // if necessary
let data = try! encoder.encode(person)
let jsonString = String(data: data, encoding: .utf8)!
print(jsonString)
/*
prints:
{
"firstName" : "John",
"lastName" : "Doe"
}
*/
Usage #2 (decode a JSON string into a Person instance):
let jsonString = """
{
"firstName" : "John",
"lastName" : "Doe"
}
"""
let jsonData = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let person = try! decoder.decode(Person.self, from: jsonData)
dump(person)
/*
prints:
▿ __lldb_expr_609.Person #0
- firstName: "John"
- lastName: "Doe"
*/
There is a Foundation class called NSJSONSerialization which can do conversion to and from JSON.
The method for converting from JSON to an object looks like this:
let jsonObject = NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.MutableContainers,
error: &error) as NSDictionary
Note that the first argument to this method is the JSON data, but not as a string object, instead as a NSData object (which is how you'll often times get JSON data anyway).
You most likely will want a factory method for your class that takes JSON data as an argument, makes use of this method and returns an initialize object of your class.
To inverse this process and create JSON data out of an object, you'll want to make use of dataWithJSONObject, in which you'll pass an object that can be converted into JSON and have an NSData? returned. Again, you'll probably want to create a helper method that requires no arguments as an instance method of your class.
As far as I know, the easiest way to handle this is to create a way to map your objects properties into a dictionary and pass that dictionary for turning your object into JSON data. Then when turning your JSON data into the object, expect a dictionary to be returned and reverse the mapping process. There may be an easier way though.
You can achieve this by using ObjectMapper library. It'll give you more control on variable names and the values and the prepared JSON. After adding this library extend the Mappable class and define mapping(map: Map) function.
For example
class User: Mappable {
var id: Int?
var name: String?
required init?(_ map: Map) {
}
// Mapping code
func mapping(map: Map) {
name <- map["name"]
id <- map["id"]
}
}
Use it like below
let user = Mapper<User>().map(JSONString)
First, create a Swift object like this
struct Person {
var firstName: String?;
var lastName: String?;
init() {
}
}
After that, serialize your JSON data you retrieved, using the built-in NSJSONSerialization and parse the values into the Person object.
var person = Person();
var error: NSError?;
var response: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(), error: &error);
if let personDictionary = response as? NSDictionary {
person.firstName = personDictionary["firstName"] as? String;
person.lastName = personDictionary["lastName"] as? String;
}
UPDATE:
Also please take a look at those libraries
Swift-JsonSerialiser
ROJSONParser
Take a look at NSKeyValueCoding.h, specifically setValuesForKeysWithDictionary. Once you deserialize the json into a NSDictionary, you can then create and initialize your object with that dictionary instance, no need to manually set values on the object. This will give you an idea of how the deserialization could work with json, but you will soon find out you need more control over deserialization process. This is why I implement a category on NSObject which allows fully controlled NSObject initialization with a dictionary during json deserialization, it basically enriches the object even further than setValuesForKeysWithDictionary can do. I also have a protocol used by the json deserializer, which allows the object being deserialized to control certain aspects, for example, if deserializing an NSArray of objects, it will ask that object what is the type name of the objects stored in the array.