42

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:

  1. 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?
  2. 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);
nhgrif
  • 61,578
  • 25
  • 134
  • 173
Marius Schulz
  • 15,976
  • 12
  • 63
  • 97
  • Generally in Swift/Objective-C, serialization will mean converting to `NSData` so the object can be stored as data rather than some plain-text format. – nhgrif Nov 08 '14 at 18:46
  • No need for 3rd party Json.NET here, DataContractJsonSerializer can do that. – Agent_L Apr 07 '16 at 11:35
  • 1
    @Agent_L I don't want to get too deeply into this debate here, but Newtonsoft.Json is a lot nicer to use and more feature-rich than `DataContractJsonSerializer`. – Marius Schulz Apr 07 '16 at 11:37
  • I'd vote you up another if I could just for using C#'s JSON.net as an (awesome) example! – William T. Mallard Feb 05 '18 at 08:07

7 Answers7

29

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.

Alex Nolasco
  • 18,750
  • 9
  • 86
  • 81
  • Is it possible to have a property like a dictionary as part of the struct and still use Codable? E.g. var items: [String: Any]? . Would codable work in that case? I feel like, it will not. – lionserdar Jun 09 '17 at 14:52
  • Do you know if the `Codable` infrastructure can be reused for other encodings. A binary encoding in the style of Protocol Buffers, for example. – Benjohn Sep 08 '17 at 14:19
  • 1
    This is nice but how do you decode multiple items? In the case when my payload contains an array of Person.self ? – chrisl08 Oct 04 '17 at 07:13
20

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

Edwin Vermeer
  • 13,017
  • 2
  • 34
  • 58
10

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"
 */
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
6

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.

nhgrif
  • 61,578
  • 25
  • 134
  • 173
  • 2
    I know about `NSJSONSerialization` and working with dictionaries, but this is specifically *not* what I'm looking for. Doing all that manually is an error-prone way of writing boilerplate code which I'm trying to avoid here. – Marius Schulz Nov 08 '14 at 19:05
  • 2
    You can use `NSCoding` but it'll still require some boilerplate code. See: http://nshipster.com/nscoding/ – DPlusV Nov 08 '14 at 19:16
4

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)
Penkey Suresh
  • 5,816
  • 3
  • 36
  • 55
0

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

Eddy Liu
  • 1,463
  • 9
  • 6
  • 1
    This approach works, but it's not an answer to my question, I'm afraid. I was looking for a solution that **doesn't** require manually setting each property. – Marius Schulz Mar 01 '15 at 11:14
  • 1
    @MariusSchulz I think that answer "there is no such 1st party thing" is also an important answer, although it could be more clear here. – Agent_L Apr 07 '16 at 11:31
0

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.

simplatek
  • 637
  • 7
  • 7