Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added option to encode null values #26

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
42 changes: 16 additions & 26 deletions JSONCodable.playground/Contents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ We'll add conformance to `JSONEncodable`. You may also add conformance to `JSONC
*/

extension User: JSONEncodable {
func toJSON() throws -> AnyObject {
return try JSONEncoder.create({ (encoder) -> Void in
func toJSON(options: JSONEncodingOptions) throws -> AnyObject {
return try JSONEncoder.create(options) { (encoder) -> Void in
try encoder.encode(id, key: "id")
try encoder.encode(name, key: "full_name")
try encoder.encode(email, key: "email")
try encoder.encode(company, key: "company")
try encoder.encode(friends, key: "friends")
})
}
}
}

Expand All @@ -54,31 +54,21 @@ We'll add conformance to `JSONDecodable`. You may also add conformance to `JSONC
*/

extension User: JSONDecodable {
init?(JSONDictionary: JSONObject) {
let decoder = JSONDecoder(object: JSONDictionary)
do {
id = try decoder.decode("id")
name = try decoder.decode("full_name")
email = try decoder.decode("email")
company = try decoder.decode("company")
friends = try decoder.decode("friends")
}
catch {
return nil
}
init(object: JSONObject) throws {
let decoder = JSONDecoder(object: object)
id = try decoder.decode("id")
name = try decoder.decode("full_name")
email = try decoder.decode("email")
company = try decoder.decode("company")
friends = try decoder.decode("friends")
}
}

extension Company: JSONDecodable {
init?(JSONDictionary: JSONObject) {
let decoder = JSONDecoder(object: JSONDictionary)
do {
name = try decoder.decode("name")
address = try decoder.decode("address")
}
catch {
return nil
}
init(object: JSONObject) throws {
let decoder = JSONDecoder(object: object)
name = try decoder.decode("name")
address = try decoder.decode("address")
}
}

Expand Down Expand Up @@ -115,7 +105,7 @@ We can instantiate `User` using one of provided initializers:
- `init?(JSONString: String)`
*/

let user = User(JSONDictionary: JSON)!
var user = try! User(object: JSON)

print("Decoded: \n\(user)\n\n")

Expand All @@ -126,7 +116,7 @@ And encode it to JSON using one of the provided methods:
*/

do {
let dict = try user.toJSON()
let dict = try user.toJSON([])
print("Encoded: \n\(dict as! JSONObject)\n\n")
}
catch {
Expand Down
2 changes: 1 addition & 1 deletion JSONCodable.playground/contents.xcplayground
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='osx' requires-full-environment='true' display-mode='rendered'>
<playground version='5.0' target-platform='osx' display-mode='rendered'>
<timeline fileName='timeline.xctimeline'/>
</playground>
5 changes: 5 additions & 0 deletions JSONCodable.playground/timeline.xctimeline
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,10 @@
<Timeline
version = "3.0">
<TimelineItems>
<LoggerValueHistoryTimelineItem
documentLocation = "#CharacterRangeLen=46&amp;CharacterRangeLoc=1785&amp;EndingColumnNumber=51&amp;EndingLineNumber=78&amp;StartingColumnNumber=5&amp;StartingLineNumber=78&amp;Timestamp=474577879.680475"
selectedRepresentationIndex = "0"
shouldTrackSuperviewWidth = "NO">
</LoggerValueHistoryTimelineItem>
</TimelineItems>
</Timeline>
3 changes: 2 additions & 1 deletion JSONCodable/JSONCodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ extension Double: JSONCompatible {}
extension Float: JSONCompatible {}
extension Bool: JSONCompatible {}
extension Int: JSONCompatible {}
extension NSNull: JSONCompatible {}

extension JSONCompatible {
public func toJSON() throws -> AnyObject {
public func toJSON(options: JSONEncodingOptions) throws -> AnyObject {
return self as! AnyObject
}
}
101 changes: 44 additions & 57 deletions JSONCodable/JSONEncodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,28 @@ public enum JSONEncodableError: ErrorType, CustomStringConvertible {
}
}

// Struct -> Dictionary
public struct JSONEncodingOptions: OptionSetType {
public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue;
}
public static let EncodeNulls = JSONEncodingOptions(rawValue: 1 << 0)
}

public protocol JSONEncodable {
func toJSON() throws -> AnyObject
func toJSON(options: JSONEncodingOptions) throws -> AnyObject
}

public extension JSONEncodable {
func toJSON() throws -> AnyObject {

func toJSON(options: JSONEncodingOptions) throws -> AnyObject {
let mirror = Mirror(reflecting: self)

guard let style = mirror.displayStyle where style == .Struct || style == .Class else {
throw JSONEncodableError.IncompatibleTypeError(elementType: self.dynamicType)
}

return try JSONEncoder.create({ (encoder) -> Void in
return try JSONEncoder.create(options) { (encoder) -> Void in
// loop through all properties (instance variables)
for (labelMaybe, valueMaybe) in mirror.children {
guard let label = labelMaybe else {
Expand Down Expand Up @@ -87,18 +94,18 @@ public extension JSONEncodable {
throw JSONEncodableError.ChildIncompatibleTypeError(key: label, elementType: value.dynamicType)
}
}
})
}
}
}

public extension Array {//where Element: JSONEncodable {
private var wrapped: [Any] { return self.map{$0} }

public func toJSON() throws -> AnyObject {
public func toJSON(options: JSONEncodingOptions) throws -> AnyObject {
var results: [AnyObject] = []
for item in self.wrapped {
if let item = item as? JSONEncodable {
results.append(try item.toJSON())
results.append(try item.toJSON(options))
}
else {
throw JSONEncodableError.ArrayIncompatibleTypeError(elementType: item.dynamicType)
Expand All @@ -111,11 +118,11 @@ public extension Array {//where Element: JSONEncodable {
// Dictionary convenience methods

public extension Dictionary {//where Key: String, Value: JSONEncodable {
public func toJSON() throws -> AnyObject {
public func toJSON(options: JSONEncodingOptions) throws -> AnyObject {
var result: [String: AnyObject] = [:]
for (k, item) in self {
if let item = item as? JSONEncodable {
result[String(k)] = try item.toJSON()
result[String(k)] = try item.toJSON(options)
}
else {
throw JSONEncodableError.DictionaryIncompatibleTypeError(elementType: item.dynamicType)
Expand All @@ -129,9 +136,11 @@ public extension Dictionary {//where Key: String, Value: JSONEncodable {

public class JSONEncoder {
var object = JSONObject()
public var options: JSONEncodingOptions = []

public static func create(@noescape setup: (encoder: JSONEncoder) throws -> Void) rethrows -> JSONObject {
public static func create(options: JSONEncodingOptions, @noescape setup: (encoder: JSONEncoder) throws -> Void) rethrows -> JSONObject {
let encoder = JSONEncoder()
encoder.options = options
try setup(encoder: encoder)
return encoder.object
}
Expand All @@ -145,21 +154,18 @@ public class JSONEncoder {
*/

// JSONEncodable
public func encode<Encodable: JSONEncodable>(value: Encodable, key: String) throws {
let result = try value.toJSON()
object[key] = result
}
private func encode(value: JSONEncodable, key: String) throws {
let result = try value.toJSON()
public func encode(value: JSONEncodable, key: String) throws {
let result = try value.toJSON(options)
object[key] = result
}

// JSONEncodable?
public func encode<Encodable: JSONEncodable>(value: Encodable?, key: String) throws {
guard let actual = value else {
if options.contains(.EncodeNulls) { object[key] = NSNull() }
return
}
let result = try actual.toJSON()
let result = try actual.toJSON(options)
object[key] = result
}

Expand All @@ -168,116 +174,96 @@ public class JSONEncoder {
guard let compatible = value.rawValue as? JSONCompatible else {
return
}
let result = try compatible.toJSON()
let result = try compatible.toJSON(options)
object[key] = result
}

// Enum?
public func encode<Enum: RawRepresentable>(value: Enum?, key: String) throws {
guard let actual = value else {
if options.contains(.EncodeNulls) { object[key] = NSNull() }
return
}
guard let compatible = actual.rawValue as? JSONCompatible else {
return
}
let result = try compatible.toJSON()
let result = try compatible.toJSON(options)
object[key] = result
}

// [JSONEncodable]
public func encode<Encodable: JSONEncodable>(array: [Encodable], key: String) throws {
guard array.count > 0 else {
return
}
let result = try array.toJSON()
let result = try array.toJSON(options)
object[key] = result
}
public func encode(array: [JSONEncodable], key: String) throws {
guard array.count > 0 else {
return
}
let result = try array.toJSON()
let result = try array.toJSON(options)
object[key] = result
}
private func encode(array: JSONArray, key: String) throws {
guard array.count > 0 && array.elementsAreJSONEncodable() else {
func encode(array: JSONArray, key: String) throws {
guard array.elementsAreJSONEncodable() else {
return
}
let encodable = array.elementsMadeJSONEncodable()
let result = try encodable.toJSON()
let result = try encodable.toJSON(options)
object[key] = result
}

// [JSONEncodable]?
public func encode<Encodable: JSONEncodable>(value: [Encodable]?, key: String) throws {
guard let actual = value else {
if options.contains(.EncodeNulls) { object[key] = NSNull() }
return
}
guard actual.count > 0 else {
return
}
let result = try actual.toJSON()
let result = try actual.toJSON(options)
object[key] = result
}

// [Enum]
public func encode<Enum: RawRepresentable>(value: [Enum], key: String) throws {
guard value.count > 0 else {
return
}
let result = try value.flatMap {
try ($0.rawValue as? JSONCompatible)?.toJSON()
try ($0.rawValue as? JSONCompatible)?.toJSON(options)
}
object[key] = result
}

// [Enum]?
public func encode<Enum: RawRepresentable>(value: [Enum]?, key: String) throws {
guard let actual = value else {
return
}
guard actual.count > 0 else {
if options.contains(.EncodeNulls) { object[key] = NSNull() }
return
}
let result = try actual.flatMap {
try ($0.rawValue as? JSONCompatible)?.toJSON()
try ($0.rawValue as? JSONCompatible)?.toJSON(options)
}
object[key] = result
}

// [String:JSONEncodable]
public func encode<Encodable: JSONEncodable>(dictionary: [String:Encodable], key: String) throws {
guard dictionary.count > 0 else {
return
}
let result = try dictionary.toJSON()
let result = try dictionary.toJSON(options)
object[key] = result
}
public func encode(dictionary: [String:JSONEncodable], key: String) throws {
guard dictionary.count > 0 else {
return
}
let result = try dictionary.toJSON()
let result = try dictionary.toJSON(options)
object[key] = result
}
private func encode(dictionary: JSONDictionary, key: String) throws {
guard dictionary.count > 0 && dictionary.valuesAreJSONEncodable() else {
func encode(dictionary: JSONDictionary, key: String) throws {
guard dictionary.valuesAreJSONEncodable() else {
return
}
let encodable = dictionary.valuesMadeJSONEncodable()
let result = try encodable.toJSON()
let result = try encodable.toJSON(options)
object[key] = result
}

// [String:JSONEncodable]?
public func encode<Encodable: JSONEncodable>(value: [String:Encodable]?, key: String) throws {
guard let actual = value else {
if options.contains(.EncodeNulls) { object[key] = NSNull() }
return
}
guard actual.count > 0 else {
return
}
let result = try actual.toJSON()
let result = try actual.toJSON(options)
object[key] = result
}

Expand All @@ -292,6 +278,7 @@ public class JSONEncoder {
// JSONTransformable?
public func encode<EncodedType, DecodedType>(value: DecodedType?, key: String, transformer: JSONTransformer<EncodedType, DecodedType>) throws {
guard let actual = value else {
if options.contains(.EncodeNulls) { object[key] = NSNull() }
return
}
guard let result = transformer.encoding(actual) else {
Expand Down
2 changes: 1 addition & 1 deletion JSONCodable/JSONString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public extension JSONEncodable {
case is Bool, is Int, is Float, is Double:
return String(self)
default:
let json = try toJSON()
let json = try toJSON([])
let data = try NSJSONSerialization.dataWithJSONObject(json, options: NSJSONWritingOptions(rawValue: 0))
guard let string = NSString(data: data, encoding: NSUTF8StringEncoding) else {
return ""
Expand Down
4 changes: 2 additions & 2 deletions JSONCodableTests/EnumTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class EnumTests: XCTestCase {
}

func testEncodingEnum() {
guard let json = try? decodedValue.toJSON() else {
guard let json = try? decodedValue.toJSON([]) else {
XCTFail()
return
}
Expand All @@ -45,7 +45,7 @@ class EnumTests: XCTestCase {

XCTAssertEqual(castedJSON, encodedValue)

guard let json2 = try? decodedValue2.toJSON() else {
guard let json2 = try? decodedValue2.toJSON([]) else {
XCTFail()
return
}
Expand Down
Loading