CocoaPods trunk is moving to be read-only. Read more on the blog, there are 12 months to go.
| TestsTested | ✓ |
| LangLanguage | SwiftSwift |
| License | MIT |
| ReleasedLast Release | Oct 2016 |
| SwiftSwift Version | 3.0 |
| SPMSupports SPM | ✗ |
Maintained by Sean G. Young.
A library seeking to provide an automatic and type-safe approach to converting Swift types to and from JSON.
SGYSwiftJSON is a library that seeks to dramatically simplify serialization and deserialization of Swift only models. The primary goal of this library is to eliminate the majority of the code required to convert arbitrary objects into JSON and vice versa. This includes recursive conversion of types contained in collections, dictionaries, and complex objects. Out-of-the-box functionality includes support for the majority of common Foundation types and a ready to inherit base class for complex types (not required, but easier). Protocols are provided which allow extending functionality to unusual objects.
The majority of models written with JSON serialization in mind are already supported. Any object graph that conforms to the following should work out-of-the-box:
Array, Set, NSArray, or NSMutableArray types with an element type that adheres to this collection of rules.String or NSString key type and a value type which adheres to this collection of rules.JSONKeyValueCreatable. This is most easily achieved by using JSONCreatableObject as the base class.NSNumber, NSDecimalNumber (may be declared optional) or can be bridged to NSNumber (cannot be declared optional).If you do not wish to have to adhere to the above limitations then it is possible to extend most other types using the defined protocols. Details on these protocols and how they’re evaluated during serialization and deserialization can be found below.
Note: Several of the above limitations do not apply or are much more lenient if you do not wish to implement deserialization.
Serialization is supported via protocols and the use of Swift’s Mirror. Any object passed to be serialized is checked for the following conditions:
JSONProxyProvider - The jsonProxy property of the object will be retrieved and passed through this same logic tree.JSONLeafRepresentable - Objects adhering to this protocol can be represented as a JSON leaf object. I.e NSString, NSNumber, or NSNull. Beyond the 3 leaf values accepted the structs String, Bool, Int, Float, and Double conform to this protocol.Date - If the object is an Date and does not conform to any of the above protocols then the dateConversionBlock, if provided, is used to convert to JSONLeafValue or the property is skipped if no block exists.SGYDictionaryReflection - The object will be converted to a dictionary of strings keys and object values. The generic Dictionary and NSDictionary both adhere to this protocol.SGYCollectionReflection - The object’s contained elements will be converted and put into an array. Array, NSArray, and Set adhere to this protocol.Mirror and converted to a dictionary.Deserialization is considerably more difficult than serialization as it requires all types have a parameterless initializer, can assign arbitrary values, and are able to report the types they contain. Upon deserialization of an NSArray or NSDictionary (the only objects produced by JSONSerialization) the following logic is performed:
JSONKeyValueCreatable the object’s properties and type values are determined using Mirror. The object produced by JSONSerialization must be an NSDictionaryor an error is thrown.JSONDictionaryCreatable the object’s key and value type are retrieved using the protocol’s keyValueTypes property. The object produced by JSONSerialization must be an NSDictionaryor an error is thrown.JSONCollectionCreatable the object’s element type is retrieved using the protocol’s elementType property. The object produced by JSONSerialization must be an NSArrayor an error is thrown.Element type. Similarly, dictionaries have the containing values converted to their Value type. For complex objects the value is converted using its Mirror property representation. This conversion is done using the following logic: Any, AnyObject or the declared type matches the deserialized type then the deserialized type is assigned directly.Date then the dateConversionBlock is used to convert the deserialized Any value to Date. If the block is not declared or returns nil the property is not assigned.JSONLeafConvertable and will be constructed using the leaf value and assigned. Otherwise the deserialized value is not assigned.[Any] type and the declared type is JSONCollectionCreatable an array will be initialized and returned using the array conversion logic. Otherwise the deserialized value is not assigned.[String: Any] type and the declared type is JSONDictionaryCreatable an array will be initialized and returned using the dictionary conversion logic. Otherwise the deserialized value is not assigned.All the examples will expand upon an arbitrary Person class. We’ll begin with a very basic class and modify/expand it to utilize more complicated/Swifty properties.
Let’s begin with a model that requires nothing to convert:
class Person {
var name: String?
var birthdate: String?
var favoriteColor: String?
var bestFriends: [Person]?
var categorizedFriends: [String: Person]?
var followersCount: NSNumber?
}Serialization is simple. Assume someGuy is an instance of Person with arbitrary values:
let serializer = SGYJSONSerializer()
do {
let jsonData = try serializer.serialize(someGuy)
} catch let error as NSError {
// Optionally catch specific errors
}Deserialization requires one simple change. We need a way to assign properties to the class. The easiest way to do this is inheriting from JSONCreatableObject which implements JSONKeyValueCreatable via NSObject’s key value coding:
class Person: JSONCreatableObjectThen deserialize:
let deserializer = SGYJSONDeserializer()
do {
let jsonGuy: Person = try deserializer.deserialize(jsonData)
} catch let error as NSError {
// Optionally catch specific errors
}The above example is already way less effort than normal conversion. But we’re still using that ObjC object NSNumber and that’s annoying to unwrap. Luckily for serialization the automatic Swift bridging does our work for us. We can simply redefine followersCount to:
var followersCount: Int?This will serialize fine. But deserialization is a problem. The property followersCount will be properly converted to an Int. But JSONCreatableObject uses key value coding so attempting to assign to Int? will throw an error. The solution is simple:
var followersCount: Int = 0 // No reason to be nil anyway. If value doesn't exist 0 is reasonable.This will deserialize just fine. If you’re hell bent on using optional Foundation types then see the next section’s info on overriding setValue:forProperty.
Now the model looks a bit closer to something we might actually design without de/serialization in mind. But what about the favoriteColor property? That absolutely begs to be a Swift enum. Serialization is, again, simpler to perform. We define Color enum that conforms to a compound protocol:
enum Color: String, JSONLeafEnum {
case red = "Red", green = "Green", blue = "Blue", yellow = "Yellow"
}Now modify the type on Person:
var favoriteColor: Color?This will serialize just fine and produce the associated rawValue as the JSON value. Deserialization is more difficult for the same reasons as Int. Except Swift enums cannot be assigned via KVC at all. The only option is to override setValue:property::
override func setValue(value: Any, property: String) throws {
if property == "color" { color = value as? Color }
else { try super.setValue(value, property: property) }
}
Now the Person class will deserialize the JSON value into an optional Swift enum.
Strictly a date is not a JSON value. But its use, and therefore need to convert, is constant. Because there are so many ways to represent a date value the serializer and deserializer classes expose a dateConversionBlock property for the explicit purpose of conversion. First let’s convert birthdate to a Date type:
var birthdate: Date?Since most JSON representations of a date are string or number types the date conversion block on SGYJSONSerializer is expected to return a JSONLeafValue enum. Let’s assume whatever consumes our JSON expects dates in DateFormatter’s MediumStyle. Then the only addition to our serialization from before is the assignment of this block:
let formatter = DateFormatter()
formatter.dateStyle = .MediumStyle
serializer.dateConversionBlock = { (date) in formatter.stringFromDate(date) }
// Continue serialization as beforeDeserialization is similar. The main difference is the dateConversionBlock on the deserializer accepts a more general argument of Any in order to allow conversion to Date from any arbitrarily deserialized value:
let formatter = DateFormatter()
formatter.dateStyle = .mediumStyle
deserializer.dateConversionBlock = { (jsonValue) -> Date? in
guard let value = jsonValue as? String else { return nil }
return formatter.dateFromString(value)
}
// Continue deserialization as before