Skip to content

Instantly share code, notes, and snippets.

@rugheid
Last active December 5, 2017 10:13
Show Gist options
  • Save rugheid/78c39e7285e52cb275d9 to your computer and use it in GitHub Desktop.
Save rugheid/78c39e7285e52cb275d9 to your computer and use it in GitHub Desktop.
This is a dictionary coder and JSON serialiser that uses NSCoding. You can use it to convert any object conforming to NSCoding to dictionaries and JSON. I wrote a blog post about how to use this class and how it works here: http://www.rugenheidbuchel.be/2015/10/25/nscoding-to-dictionary-and-json/
//
// RHCustomObjectCoder.swift
//
// Created by Rugen Heidbuchel on 13/10/15.
// Copyright © 2015 Rugen Heidbuchel. All rights reserved.
//
import Foundation
class RHCustomObjectCoder: NSCoder {
// MARK: Properties
private var dictionary = [String:AnyObject]()
// MARK: Settings
var base64EncodingOptions = NSDataBase64EncodingOptions(rawValue: 0)
var jsonWriteOptions = NSJSONWritingOptions.PrettyPrinted
// MARK: Dictionary Coding
class func encodeObjectAsDictionary(object: NSCoding,
alsoEncodeSubobjects: Bool = false,
jsonCompatibleEncoding: Bool = false)
-> [String: AnyObject] {
let coder = RHCustomObjectCoder()
return coder.encodeObjectAsDictionary(object,
alsoEncodeSubobjects: alsoEncodeSubobjects,
jsonCompatibleEncoding: jsonCompatibleEncoding)
}
func encodeObjectAsDictionary(object: NSCoding,
alsoEncodeSubobjects: Bool = false,
jsonCompatibleEncoding: Bool = false)
-> [String: AnyObject] {
object.encodeWithCoder(self)
if !(alsoEncodeSubobjects || jsonCompatibleEncoding) {
return dictionary
} else {
return self.safeVersionOfObject(dictionary,
alsoEncodeSubobjects: alsoEncodeSubobjects,
jsonCompatibleEncoding: jsonCompatibleEncoding) as! [String: AnyObject]
}
}
// MARK: Array Coding
class func encodeArrayOfCustomObjects <CustomObjectType: NSCoding>
(objects: [CustomObjectType],
alsoEncodeSubobjects: Bool = false,
jsonCompatibleEncoding: Bool = false)
-> [[String: AnyObject]] {
let coder = RHCustomObjectCoder()
return coder.encodeArrayOfCustomObjects(objects,
alsoEncodeSubobjects: alsoEncodeSubobjects,
jsonCompatibleEncoding: jsonCompatibleEncoding)
}
func encodeArrayOfCustomObjects <CustomObjectType: NSCoding>
(objects: [CustomObjectType],
alsoEncodeSubobjects: Bool = false,
jsonCompatibleEncoding: Bool = false)
-> [[String: AnyObject]] {
var encodedArray = [[String: AnyObject]]()
for object in objects {
encodedArray.append(RHCustomObjectCoder.encodeObjectAsDictionary(object,
alsoEncodeSubobjects: alsoEncodeSubobjects,
jsonCompatibleEncoding: jsonCompatibleEncoding))
}
return encodedArray
}
// MARK: JSON Coding
class func encodeObjectAsJSONString(object: NSCoding) throws -> String {
let coder = RHCustomObjectCoder()
return try! coder.encodeObjectAsJSONString(object)
}
func encodeObjectAsJSONString(object: NSCoding) throws -> String {
let jsonData = try! NSJSONSerialization.dataWithJSONObject(
self.encodeObjectAsDictionary(object,
alsoEncodeSubobjects: true, jsonCompatibleEncoding: true),
options: self.jsonWriteOptions)
return String(data: jsonData, encoding: NSUTF8StringEncoding)!
}
class func encodeArrayOfCustomObjectsAsJSONString <CustomObjectType: NSCoding>
(objects: [CustomObjectType]) throws -> String {
let coder = RHCustomObjectCoder()
return try! coder.encodeArrayOfCustomObjectsAsJSONString(objects)
}
func encodeArrayOfCustomObjectsAsJSONString <CustomObjectType: NSCoding>
(objects: [CustomObjectType]) throws -> String {
let jsonData = try! NSJSONSerialization.dataWithJSONObject(
self.encodeArrayOfCustomObjects(objects,
alsoEncodeSubobjects: true,
jsonCompatibleEncoding: true),
options: self.jsonWriteOptions)
return String(data: jsonData, encoding: NSUTF8StringEncoding)!
}
// MARK: - NSCoder Overrides
override var allowsKeyedCoding: Bool {
return true
}
override func containsValueForKey(key: String) -> Bool {
return dictionary[key] != nil
}
// MARK: Encoding
override func encodeBool(boolv: Bool, forKey key: String) {
dictionary[key] = NSNumber(bool: boolv)
}
override func encodeBytes(bytesp: UnsafePointer<UInt8>, length lenv: Int, forKey key: String) {
NSException(name: "Unsupported",
reason: "The encoding of bytes is not supported.",
userInfo: nil).raise()
}
override func encodeConditionalObject(objv: AnyObject?, forKey key: String) {
NSException(name: "Unsupported",
reason: "The conditional encoding of objects is not supported.",
userInfo: nil).raise()
}
override func encodeDouble(realv: Double, forKey key: String) {
dictionary[key] = NSNumber(double: realv)
}
override func encodeFloat(realv: Float, forKey key: String) {
dictionary[key] = NSNumber(float: realv)
}
override func encodeInt(intv: Int32, forKey key: String) {
dictionary[key] = NSNumber(int: intv)
}
override func encodeInt32(intv: Int32, forKey key: String) {
dictionary[key] = NSNumber(int: intv)
}
override func encodeInt64(intv: Int64, forKey key: String) {
dictionary[key] = NSNumber(integer: Int(intv))
}
override func encodeInteger(intv: Int, forKey key: String) {
dictionary[key] = NSNumber(integer: intv)
}
override func encodeObject(objv: AnyObject?, forKey key: String) {
if let object = objv {
dictionary[key] = object
}
}
#if os(OSX)
override func encodePoint(point: NSPoint, forKey key: String) {
NSException(name: "Unsupported",
reason: "The encoding of NSPoint is not supported.",
userInfo: nil).raise()
}
override func encodeRect(rect: NSRect, forKey key: String) {
NSException(name: "Unsupported",
reason: "The encoding of NSRect is not supported.",
userInfo: nil).raise()
}
override func encodeSize(size: NSSize, forKey key: String) {
NSException(name: "Unsupported",
reason: "The encoding of NSSize is not supported.",
userInfo: nil).raise()
}
#endif
override func encodeValueOfObjCType(type: UnsafePointer<Int8>, at addr: UnsafePointer<Void>) {
NSException(name: "Unsupported",
reason: "The encoding of values of a given Objective-C type is not supported.",
userInfo: nil).raise()
}
// MARK: Private Helpers
private func dateToISOString(date: NSDate) -> String {
let dateFormatter = NSDateFormatter()
dateFormatter.timeZone = NSTimeZone(abbreviation: "UTC")
dateFormatter.dateFormat = "YYYY-MM-dd'T'HH:mm:ss.SSS'Z'"
return dateFormatter.stringFromDate(date)
}
private func safeVersionOfObject(object: AnyObject, alsoEncodeSubobjects: Bool, jsonCompatibleEncoding: Bool) -> AnyObject {
if object is String || object is NSNumber {
return object
} else if let date = object as? NSDate {
return jsonCompatibleEncoding ? self.dateToISOString(date) : date
} else if let dictionary = object as? NSDictionary {
let safeDictionary = NSMutableDictionary(capacity: dictionary.count)
for (key, value) in dictionary {
safeDictionary[key as! NSCopying] = self.safeVersionOfObject(value,
alsoEncodeSubobjects: alsoEncodeSubobjects,
jsonCompatibleEncoding: jsonCompatibleEncoding)
}
return safeDictionary
} else if let array = object as? [AnyObject] {
var safeArray = [AnyObject]()
for value in array {
safeArray.append(self.safeVersionOfObject(value,
alsoEncodeSubobjects: alsoEncodeSubobjects,
jsonCompatibleEncoding: jsonCompatibleEncoding))
}
return safeArray
} else if let orderedSet = object as? NSOrderedSet {
return self.safeVersionOfObject(orderedSet.array,
alsoEncodeSubobjects: alsoEncodeSubobjects,
jsonCompatibleEncoding: jsonCompatibleEncoding)
} else if let set = object as? NSSet {
return self.safeVersionOfObject(set.allObjects,
alsoEncodeSubobjects: alsoEncodeSubobjects,
jsonCompatibleEncoding: jsonCompatibleEncoding)
} else if let data = object as? NSData {
return jsonCompatibleEncoding ?
data.base64EncodedStringWithOptions(self.base64EncodingOptions) :
data
} else if let codingCompliant = object as? NSCoding where alsoEncodeSubobjects || jsonCompatibleEncoding {
return RHCustomObjectCoder.encodeObjectAsDictionary(codingCompliant,
alsoEncodeSubobjects: alsoEncodeSubobjects,
jsonCompatibleEncoding: jsonCompatibleEncoding)
} else {
NSException(name: "Unsupported",
reason: "The given object is not NSCoding compliant.",
userInfo: nil).raise()
fatalError()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment