Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Demo/Demo/User.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct User {
var company: Company?
var friends: [User] = []
var website: NSURL?
var props: [String: String] = [:]
}

extension User: JSONCodable {
Expand All @@ -27,6 +28,7 @@ extension User: JSONCodable {
company = try JSONDictionary.decode("company")
friends = try JSONDictionary.decode("friends")
website = try JSONDictionary.decode("website.url", transformer: JSONTransformers.StringToNSURL)
props = try JSONDictionary.decode("props")
}
catch {
print(error)
Expand All @@ -42,6 +44,7 @@ extension User: JSONCodable {
try result.encode(company, key: "company")
try result.encode(friends, key: "friends")
try result.encode(website, key: "website", transformer: JSONTransformers.StringToNSURL)
try result.encode(props, key: "props")
return result
}
}
3 changes: 2 additions & 1 deletion Demo/Demo/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ class ViewController: UIViewController {
["id": 27, "full_name": "Bob Jefferson"],
["id": 29, "full_name": "Jen Jackson"]
],
"website": ["url": "http://johnappleseed.com"]
"website": ["url": "http://johnappleseed.com"],
"props": ["prop a": "value a", "prop b": "value b"]
]

print("Initial JSON:\n\(JSON)\n\n")
Expand Down
25 changes: 25 additions & 0 deletions JSONCodable/JSONDecodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,28 @@ public extension Dictionary where Value: AnyObject {
}
}

// optional array of scalars
public func decode<Value: JSONCompatible>(key: Key) throws -> [String: Value]? {
if let y = get(key) ?? self[key] {
guard let x = y as? [String: Value] else {
throw JSONDecodableError.IncompatibleTypeError(key: key as! String, elementType: y.dynamicType, expectedType: [String: Value].self)
}
return x
}
return nil
}

// required dictionary of scalars
public func decode<Value: JSONCompatible>(key: Key) throws -> [String: Value] {
guard let y = get(key) ?? self[key] else {
throw JSONDecodableError.MissingTypeError(key: key as! String)
}
guard let x = y as? [String: Value] else {
throw JSONDecodableError.IncompatibleTypeError(key: key as! String, elementType: y.dynamicType, expectedType: [String: Value].self)
}
return x
}

// TODO: validate array elements
// optional array of decodables
public func decode<Element: JSONDecodable>(key: Key) throws -> [Element]? {
Expand Down Expand Up @@ -130,6 +152,9 @@ public extension Dictionary where Value: AnyObject {
// optional decodable
public func decode<Type: JSONDecodable>(key: Key) throws -> Type? {
if let y = get(key) ?? self[key] {
if y is NSNull {
return nil
}
guard let x = y as? [String : AnyObject] else {
throw JSONDecodableError.DictionaryTypeExpectedError(key: key as! String, elementType: y.dynamicType)
}
Expand Down
27 changes: 27 additions & 0 deletions JSONCodable/JSONEncodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
public enum JSONEncodableError: ErrorType, CustomStringConvertible {
case IncompatibleTypeError(elementType: Any.Type)
case ArrayIncompatibleTypeError(elementType: Any.Type)
case DictionaryIncompatibleTypeError(elementType: Any.Type)
case ChildIncompatibleTypeError(key: String, elementType: Any.Type)
case TransformerFailedError(
key: String
Expand All @@ -22,6 +23,8 @@ public enum JSONEncodableError: ErrorType, CustomStringConvertible {
return "JSONEncodableError: Incompatible type \(elementType)"
case let .ArrayIncompatibleTypeError(elementType: elementType):
return "JSONEncodableError: Got an array of incompatible type \(elementType)"
case let .DictionaryIncompatibleTypeError(elementType: elementType):
return "JSONEncodableError: Got an dictionary of incompatible type \(elementType)"
case let .ChildIncompatibleTypeError(key: key, elementType: elementType):
return "JSONEncodableError: Got incompatible type \(elementType) for key \(key)"
case let .TransformerFailedError(key: key):
Expand Down Expand Up @@ -77,6 +80,21 @@ public extension Array {//where Element: JSONEncodable {

// Dictionary convenience methods

public extension Dictionary {//where Key: String, Value: JSONEncodable {
public func toJSON() throws -> AnyObject {
var result: [String: AnyObject] = [:]
for (k, item) in self {
if let item = item as? JSONEncodable {
result[String(k)] = try item.toJSON()
}
else {
throw JSONEncodableError.DictionaryIncompatibleTypeError(elementType: item.dynamicType)
}
}
return result
}
}

public extension Dictionary where Value: AnyObject {
public mutating func encode(value: Any, key: Key) throws {
let actualValue: Any
Expand Down Expand Up @@ -107,6 +125,15 @@ public extension Dictionary where Value: AnyObject {
self[key] = (result as! Value)
}

// test for dictionary
else if let dict = actualValue as? JSONDictionary {
if dict.dictionaryIsJSONEncodable() {
let encodableDict = dict.dictionaryMadeJSONEncodable()
let result = try encodableDict.toJSON()
self[key] = (result as! Value)
}
}

// incompatible type
else {
throw JSONEncodableError.ChildIncompatibleTypeError(key: key as! String, elementType: actualValue.dynamicType)
Expand Down
21 changes: 21 additions & 0 deletions JSONCodable/JSONHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,27 @@
// Copyright © 2015 matthewcheok. All rights reserved.
//

// Dictionary handling

protocol JSONDictionary {
func dictionaryIsJSONEncodable() -> Bool
func dictionaryMadeJSONEncodable() -> [String: JSONEncodable]
}

extension Dictionary : JSONDictionary {
func dictionaryIsJSONEncodable() -> Bool {
return Key.self is String.Type && Value.self is JSONEncodable.Type
}

func dictionaryMadeJSONEncodable() -> [String: JSONEncodable] {
var dict: [String: JSONEncodable] = [:]
for (k, v) in self {
dict[String(k)] = v as? JSONEncodable
}
return dict
}
}

// Array handling

protocol JSONArray {
Expand Down
46 changes: 41 additions & 5 deletions JSONCodable/JSONString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,48 @@ import Foundation

public extension JSONEncodable {
public func toJSONString() throws -> String {
let json = try toJSON()
let data = try NSJSONSerialization.dataWithJSONObject(json, options: NSJSONWritingOptions(rawValue: 0))
guard let string = NSString(data: data, encoding: NSUTF8StringEncoding) else {
return ""
switch self {
case let str as String:
return escapeJSONString(str)
case is Bool, is Int, is Float, is Double:
return String(self)
default:
let json = try toJSON()
let data = try NSJSONSerialization.dataWithJSONObject(json, options: NSJSONWritingOptions(rawValue: 0))
guard let string = NSString(data: data, encoding: NSUTF8StringEncoding) else {
return ""
}
return string as String
}
}
}

private func escapeJSONString(str: String) -> String {
var chars = String.CharacterView("\"")
for c in str.characters {
switch c {
case "\\":
chars.append("\\")
chars.append("\\")
case "\"":
chars.append("\\")
chars.append("\"")
default:
chars.append(c)
}
}
chars.append("\"")
return String(chars)
}

public extension Optional where Wrapped: JSONEncodable {
public func toJSONString() throws -> String {
switch self {
case let .Some(jsonEncodable):
return try jsonEncodable.toJSONString()
case nil:
return "null"
}
return string as String
}
}

Expand Down