How to convert array of mappables to JSON? - json

I using Objectmapper and Alamofire in my project.
Let's have a struct:
struct User: Mappable {
var name = ""
init?(map: Map) {}
mutating func mapping(map: Map) {
name <- map["name"]
}
}
and then i want to make a request to send array of users to server like this:
var users = [User]()
...
let parameters = ?//i want to convert users array to JSON
Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters).responseJSON {
...
}
But i don't know how to convert users array to parameters for input to Alamofire request.

To convert an object to String using mapper:
let user = User()
let userString = Mapper<User>.toJSONString(user)
To convert it to JSON:
let userJSON = Mapper<User>().toJSON(user)
You can checkout various apis provided by ObjectMapper by command-clicking 'Mapper' in your code or go to Mapper.swift.

Related

How to map JSON String to model class using Object Mapper in swift

My model class is like:
class CalendarTaskModel: Mappable {
var kpiColor: String?
var kpi: String?
var date: String?
required init?(map: Map) {
//Code here
}
func mapping(map: Map) {
kpiColor <- map["kpiColor"]
kpi <- map["kpi"]
date <- map["date"]
}
}
I have an object mapped with a model class.
var taskDetails: [CalendarTaskModel]?
As my object is of array type so I Want to map JSON string to object using ObjectMapper like the code below.
code 1: taskDetails = Mapper<[CalendarTaskModel]>().map(JSONString: jsonStr)
//
code 2: taskDetails = Mapper<CalendarTaskModel>().map(JSONString: jsonStr)
but I am getting errors && Please suggest how to do this?
Thanks in advance.
I figured it out! You should use the mapArray method instead:
let jsonStr = ...
var taskDetails: [CalendarTaskModel]?
taskDetails = Mapper<CalendarTaskModel>().mapArray(JSONfile: jsonStr)
This is because the map method does not return an array.
As for the code 1 that you provided, the [CalendarTaskModel] type (equivalent to Array<CalendarTaskModel> is not compliant with that mappable protocol. I suspect it is possible to make it compliant, for instance with more complex logic, but the library encourages you to use the method I suggested. Best of luck!

ObjectMapper returrning NIL in Swift

I'm trying to use Object mapper https://github.com/Hearst-DD/ObjectMapper to convert a JSON string into a Swift object. Note I've simplified the object to a single field here - obviously my real response has more fields!
My response is:
data arrray response [{
chat = {
"_id" = 1;
}}]
So I want to convert to my chat class:
public class Chat: Mappable {
var _id: String? }
public required init?(map: Map){
}
public func mapping(map: Map) {
_id <- map["_id"]
}
}
So I convert my data array to a dictionary
let jsonResponse = dataArray [0]
let discResponse = jsonResponse as! Dictionary<String,AnyObject>
I can even access my field manually
let chat = discResponse["chat"]
let id = chat!["_id"]
print ("CHAT ID", id)
But mapping to the object
let jsonData = try! JSONSerialization.data(withJSONObject: chat, options: .prettyPrinted)
let user = Chat(JSONString: String( describing: jsonData))
returns nil
Why?
Just putting my comment as an answer, if someone will stuck on the same issue: use Mapper<Chat>().map(JSONObject: chat). It should help your cause.

Swift - Mapping of Nested Objects (Objectmapper)

I am developing an application with Swift 4. Where I make a call to the APIRest with Alamofire and I want to map the JSON response with Objectmapper. Well, the JSON that calls me back is the following:
The code to the APIRest is:
func retrievePostListData() {
Alamofire
.request("http://www.speedrun.com/api/v1/games", method: .get)
.validate()
.responseArray(completionHandler: { (response:
DataResponse<[PostModelSpeedRunModel]>) in
switch response.result {
case .success(let posts):
self.remoteRequestHandler?.onPostsRetrievedData(posts)
case .failure( _):
self.remoteRequestHandler?.onError()
}
})
}
The problem is that I do not know how to access each of the values (func mapping). Because there are some nested values. In addition to that some of the annunciations are objects and others are array. My erroneous code is the following:
import Foundation
import ObjectMapper
struct PostModelSpeedRunModel {
var id = ""
var international = ""
var abbreviation = ""
var links = [Links]??? // I need to get "rel" and "uri" of "runs"
var uri = ""
}
extension PostModelSpeedRunModel: Mappable {
init?(map: Map) {
}
mutating func mapping(map: Map) {
id <- map["data.id"]
international <- map["data.international"]
abbreviation <- map["data.abbreviation"]
link <- map["data.Links"]
uri <- map["data.logo"]
}
}
Can you help me do / understand doing the function mapping? Thanks
I'm assuming you are getting no values at all, because I tried your code and got nothing. If you only need the contents of the data array from the json and, as you have it now, ObjectMapper is expecting a json with just an array of PostModelSpeedRunModels. Therefore, you need to add a keyPath to tell AlamofireObjectMapper it's starting point:
Alamofire.request("http://www.speedrun.com/api/v1/games", method: .get)
.responseArray(keyPath: "data") { (response: DataResponse<[PostModelSpeedRunModel]>) in
...
}
If you also need the info from the pagination node, then you'll need to create a new object that has a data and pagination properties, and change responseArray(keyPath: ...) to simply responseObject using the new root object in DataResponse.
Then I believe you only want runs's uri, so I recommend just having a String in your model for storing it, instead of an array of Links. Assuming that the array of links is unsorted and may change order in the future (if not you can access directly like map["links.1.uri"] and you are done), all links need to be parsed and then filtered. It can be done as follows:
struct PostModelSpeedRunModel {
var id = ""
var international = ""
var abbreviation = ""
var runsLink = ""
var uri = ""
}
extension PostModelSpeedRunModel: Mappable {
init?(map: Map) {
}
mutating func mapping(map: Map) {
id <- map["id"]
international <- map["international"]
abbreviation <- map["abbreviation"]
uri <- map["logo"]
var links: [Link]?
links <- map["links"]
if let uri = links?.first(where: {$0.rel == "runs"})?.uri {
runsLink = uri
}
}
}
struct Link {
var rel = ""
var uri = ""
}
extension Link: Mappable {
init?(map: Map) {
}
mutating func mapping(map: Map) {
rel <- map["rel"]
uri <- map["uri"]
}
}

How to map dynamic properties in model class (Swift)

I am new to IOS Development, i came across a really interesting situation, i have this json response coming server side
{
"caps": {
"first_key": "34w34",
"first_char": "34w45",
"first_oddo": "34w34"
.... : .....
.... : .....
}
}
My issue this that keys inside "caps" object can be dynamic (like what if one more value is added). I am using ObjectMapper to mapper to map values from response to model class. I have this model class
class User: Mappable {
var first_key: String?
var first_char: String?
var first_oddo: String?
required init?(map: Map) {
}
// Mappable
func mapping(map: Map) {
first_key <- map["first_key"]
first_char <- map["first_char"]
first_oddo <- map["first_oddo"]
}
}
Now as i dont know how to populate my model if values in json response are changed (because that are dynamic). I hope i have explained it well. I think i dont want hard coded values inside model?
This solution uses the Codable protocol introduced with Swift 4.
If the keys of your JSON are dynamic then you need a Dictionary.
JSON
Given this JSON
let data = """
{
"caps": {
"first_key": "34w34",
"first_char": "34w45",
"first_oddo": "34w34"
}
}
""".data(using: .utf8)!
The Response Model
You can define a struct like this
struct Response:Codable {
let caps: [String:String]
}
Decoding 🎉🎉🎉
Now you can decode your JSON
if let response = try? JSONDecoder().decode(Response.self, from: data) {
print(response.caps)
}
Output
["first_key": "34w34", "first_oddo": "34w34", "first_char": "34w45"]

Automatic JSON serialization and deserialization of objects in Swift

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.