Last active
March 7, 2016 09:44
-
-
Save DeFrenZ/3ddc0bbb2ae3d2b79d74 to your computer and use it in GitHub Desktop.
Swift JSON
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// JSON.swift | |
// Boppl | |
// | |
// Created by Davide De Franceschi on 26/06/2015. | |
// Copyright (c) 2015 Boppl. All rights reserved. | |
// | |
import Foundation | |
//MARK: Foundation typealiases | |
public typealias FoundationJSONArray = [NSObject] | |
public typealias FoundationJSONObject = [String: NSObject] | |
//MARK: enum declaration | |
public enum JSON { | |
case NullValue | |
case BooleanValue(Bool) | |
case NumberValue(NSDecimal) | |
case StringValue(String) | |
case ArrayValue([JSON]) | |
case ObjectValue([String: JSON]) | |
} | |
//MARK: Errors | |
extension JSON { | |
public enum Error: ErrorType { | |
case ParsingError | |
} | |
} | |
//MARK: Printables | |
private let tabCharacter: Character = "\t" | |
private func indentationOfLevel(indentationLevel: UInt) -> String { return String(count: Int(indentationLevel), repeatedValue: tabCharacter) } | |
extension JSON: CustomStringConvertible { | |
public var description: String { | |
switch self { | |
case .NullValue: return "null" | |
case .BooleanValue(let value): return value ? "true" : "false" | |
case .NumberValue(let value): return "\(value)" | |
case .StringValue(let value): return "\"\(value)\"" | |
case .ArrayValue(let value): return "[" + value.map({ "\($0)" }).joinWithSeparator(",") + "]" | |
case .ObjectValue(let value): return "{" + value.sort({ $0.0 < $1.0 }).map({ "\"\($0)\":\($1)" }).joinWithSeparator(",") + "}" | |
} | |
} | |
} | |
extension JSON: CustomDebugStringConvertible { | |
private func debugDescriptionWithIndentationLevel(indentationLevel: UInt) -> String { | |
switch self { | |
case .ArrayValue(let value): | |
if value.isEmpty { | |
return "[]" | |
} | |
return "[\n" + value.map({ | |
"\(indentationOfLevel(indentationLevel + 1))\($0.debugDescriptionWithIndentationLevel(indentationLevel + 1))" | |
}).joinWithSeparator(",\n") + "\n\(indentationOfLevel(indentationLevel))]" | |
case .ObjectValue(let value): | |
if value.isEmpty { | |
return "{}" | |
} | |
let sortedValue = value.sort({ $0.0 < $1.0 }) | |
return "{\n" + sortedValue.map({ | |
"\(indentationOfLevel(indentationLevel + 1))\"\($0)\": \($1.debugDescriptionWithIndentationLevel(indentationLevel + 1))" | |
}).joinWithSeparator(",\n") + "\n\(indentationOfLevel(indentationLevel))}" | |
default: return description | |
} | |
} | |
public var debugDescription: String { return debugDescriptionWithIndentationLevel(0) } | |
} | |
//MARK: LiteralConvertibles | |
extension JSON: NilLiteralConvertible { | |
public init(nilLiteral: ()) { self = NullValue } | |
} | |
extension JSON: BooleanLiteralConvertible { | |
public init(booleanLiteral value: BooleanLiteralType) { self = BooleanValue(value) } | |
} | |
extension JSON: IntegerLiteralConvertible, FloatLiteralConvertible { | |
public init(integerLiteral value: IntegerLiteralType) { self = NumberValue(NSDecimal(integerLiteral: value)) } | |
public init(floatLiteral value: FloatLiteralType) { self = NumberValue(NSDecimal(floatLiteral: value)) } | |
} | |
extension JSON: StringLiteralConvertible { | |
public init(unicodeScalarLiteral value: Character) { self = StringValue(String(value)) } | |
public init(extendedGraphemeClusterLiteral value: Character) { self = StringValue(String(value)) } | |
public init(stringLiteral value: StringLiteralType) { self = StringValue(value) } | |
} | |
extension JSON: ArrayLiteralConvertible { | |
public init(arrayLiteral elements: JSON...) { self = ArrayValue(elements) } | |
} | |
extension JSON: DictionaryLiteralConvertible { | |
public init(dictionaryLiteral elements: (String, JSON)...) { | |
var dictionary = [String: JSON](minimumCapacity: elements.count) | |
for keyAndValue in elements { | |
dictionary[keyAndValue.0] = keyAndValue.1 | |
} | |
self = ObjectValue(dictionary) | |
} | |
} | |
//MARK: init from Foundation types | |
extension NSNumber { | |
private var numberType: CFNumberType { return CFNumberGetType(self) } | |
private var couldBeJSONBoolean: Bool { | |
return numberType == .SInt8Type || numberType == .CharType | |
} | |
private var couldBeJSONNumber: Bool { | |
return (numberType == .DoubleType || numberType == .Float32Type || numberType == .Float64Type || numberType == .SInt32Type || numberType == .SInt64Type) && doubleValue.isFinite | |
} | |
} | |
extension JSON { | |
public enum FoundationBridgingError: ErrorType { | |
case NSNumberNotAJSONBoolean(number: NSNumber) | |
case NSNumberNotAJSONNumber(number: NSNumber) | |
case NSDictionaryKeysNotAllStrings(dictionary: NSDictionary) | |
case ObjectNotBridgeable(object: AnyObject) | |
} | |
public init(foundationNil value: NSNull) { | |
self = NullValue | |
} | |
public init(foundationBoolean value: NSNumber) throws { | |
guard value.couldBeJSONBoolean else { throw FoundationBridgingError.NSNumberNotAJSONBoolean(number: value) } | |
self = BooleanValue(value.boolValue) | |
} | |
public init(foundationNumber value: NSNumber) throws { | |
guard value.couldBeJSONNumber else { throw FoundationBridgingError.NSNumberNotAJSONNumber(number: value) } | |
self = NumberValue(value.decimalValue) | |
} | |
public init(foundationString value: NSString) { | |
self = StringValue(value as String) | |
} | |
public init(foundationArray value: NSArray) throws { | |
var convertedArray: [JSON] = [] | |
for foundationObject in value { | |
let convertedObject = try JSON(foundationObject: foundationObject) | |
convertedArray.append(convertedObject) | |
} | |
self = ArrayValue(convertedArray) | |
} | |
public init(foundationDictionary value: NSDictionary) throws { | |
var convertedDictionary: [String: JSON] = [:] | |
for (foundationKey, foundationObject) in value { | |
guard let key = foundationKey as? String else { throw FoundationBridgingError.NSDictionaryKeysNotAllStrings(dictionary: value) } | |
let value = try JSON(foundationObject: foundationObject) | |
convertedDictionary.updateValue(value, forKey: key) | |
} | |
self = ObjectValue(convertedDictionary) | |
} | |
public init(foundationObject value: AnyObject) throws { | |
switch value { | |
case let null as NSNull: self.init(foundationNil: null) | |
case let boolean as NSNumber where boolean.couldBeJSONBoolean: try self.init(foundationBoolean: boolean) | |
case let number as NSNumber where number.couldBeJSONNumber: try self.init(foundationNumber: number) | |
case let string as NSString: self.init(foundationString: string) | |
case let array as NSArray: try self.init(foundationArray: array) | |
case let dictionary as NSDictionary: try self.init(foundationDictionary: dictionary) | |
default: throw FoundationBridgingError.ObjectNotBridgeable(object: value) | |
} | |
} | |
} | |
//MARK: properties for Foundation types | |
extension JSON { | |
public var asFoundationNil: NSNull? { | |
guard case .NullValue = self else { return nil } | |
return asFoundationObject as? NSNull | |
} | |
public var asFoundationBoolean: NSNumber? { | |
guard case .BooleanValue = self else { return nil } | |
return asFoundationObject as? NSNumber | |
} | |
public var asFoundationNumber: NSNumber? { | |
guard case .NumberValue = self else { return nil } | |
return asFoundationObject as? NSNumber | |
} | |
public var asFoundationString: NSString? { | |
guard case .StringValue = self else { return nil } | |
return asFoundationObject as? NSString | |
} | |
public var asFoundationArray: FoundationJSONArray? { | |
guard case .ArrayValue = self else { return nil } | |
return asFoundationObject as? FoundationJSONArray | |
} | |
public var asFoundationDictionary: FoundationJSONObject? { | |
guard case .ObjectValue = self else { return nil } | |
return asFoundationObject as? FoundationJSONObject | |
} | |
public var asFoundationObject: NSObject { | |
switch self { | |
case .NullValue: return NSNull() | |
case let .BooleanValue(boolean): return NSNumber(bool: boolean) | |
case let .NumberValue(number): return NSDecimalNumber(decimal: number) | |
case let .StringValue(string): return NSString(string: string) | |
case let .ArrayValue(array): return array.map({ $0.asFoundationObject }) | |
case let .ObjectValue(object): return NSDictionary(objects: object.values.map({ $0.asFoundationObject }), forKeys: object.keys.map({ $0 as NSString })) | |
} | |
} | |
} | |
//MARK: properties for Swift native types | |
extension JSON { | |
public var asBool: Bool? { | |
switch self { | |
case .BooleanValue(let boolean): return boolean | |
default: return nil | |
} | |
} | |
public var asDecimal: NSDecimal? { | |
switch self { | |
case .NumberValue(let number): return number | |
default: return nil | |
} | |
} | |
public var asInt: Int? { return asDecimal?.asInt } | |
public var asUInt: UInt? { return asDecimal?.asUInt } | |
public var asDouble: Double? { return asDecimal?.asDouble } | |
public var asString: String? { | |
switch self { | |
case .StringValue(let string): return string | |
default: return nil | |
} | |
} | |
public var asArray: [JSON]? { | |
switch self { | |
case .ArrayValue(let array): return array | |
default: return nil | |
} | |
} | |
public var asDictionary: [String: JSON]? { | |
switch self { | |
case .ObjectValue(let object): return object | |
default: return nil | |
} | |
} | |
} | |
//MARK: subscripts | |
extension JSON { | |
public subscript(arrayIndex: Int) -> JSON? { | |
switch self { | |
case .ArrayValue(let array): return arrayIndex < array.count ? array[arrayIndex] : nil | |
default: return nil | |
} | |
} | |
public subscript(objectKey: String) -> JSON? { | |
switch self { | |
case .ObjectValue(let object): return object[objectKey] | |
default: return nil | |
} | |
} | |
} | |
//MARK: String en/decoding | |
extension JSON { | |
public init(decodedFromData data: NSData) throws { | |
let decodedObject: AnyObject = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) | |
try self.init(foundationObject: decodedObject) | |
} | |
public init(decodedFromString string: String, usingEncoding encoding: NSStringEncoding = NSUTF8StringEncoding) throws { | |
let data = string.dataUsingEncoding(encoding)! | |
try self.init(decodedFromData: data) | |
} | |
public func encodeToString() -> String { | |
return description | |
} | |
public func encodeToData(encoding: NSStringEncoding = NSUTF8StringEncoding) -> NSData { | |
return encodeToString().dataUsingEncoding(encoding)! | |
} | |
} | |
//MARK: Model en/decoding | |
public protocol DataDecodable { | |
init(decodeFromData data: NSData) throws | |
} | |
public protocol JSONDecodable: DataDecodable { | |
init(decodeFromJSON json: JSON) throws | |
} | |
extension JSONDecodable { | |
public init(decodeFromData data: NSData) throws { | |
let json = try JSON(decodedFromData: data) | |
try self.init(decodeFromJSON: json) | |
} | |
} | |
extension JSON { | |
public enum ArrayParsingError: ErrorType { | |
case ElementNotDecodableType, NotAnArray | |
} | |
} | |
extension Array: JSONDecodable { | |
public init(decodeFromJSON json: JSON) throws { | |
guard let DecodableElement = Element.self as? JSONDecodable.Type else { throw JSON.ArrayParsingError.ElementNotDecodableType } | |
guard let jsonArray = json.asArray else { throw JSON.ArrayParsingError.NotAnArray } | |
self = try jsonArray.flatMap({ try DecodableElement.init(decodeFromJSON: $0) as? Element }) | |
} | |
} | |
public protocol DataEncodable { | |
func encodedData() -> NSData | |
} | |
public protocol JSONEncodable: DataEncodable { | |
func encodedJSON() -> JSON | |
} | |
extension JSONEncodable { | |
public func encodedData() -> NSData { | |
return encodedJSON().encodeToData() | |
} | |
} | |
extension JSON: JSONEncodable { | |
public func encodedJSON() -> JSON { return self } | |
} | |
extension Array where Element: JSONEncodable { | |
public func encodedJSON() -> JSON { | |
return .ArrayValue(map({ $0.encodedJSON() })) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// NSDecimal.swift | |
// Boppl | |
// | |
// Created by Davide De Franceschi on 29/06/2015. | |
// Copyright (c) 2015 Boppl. All rights reserved. | |
// | |
import Foundation | |
extension NSDecimal: Equatable {} | |
public func ==(var lhs: NSDecimal, var rhs: NSDecimal) -> Bool { | |
return NSDecimalCompare(&lhs, &rhs) == .OrderedSame | |
} | |
extension NSDecimal: Comparable {} | |
public func <(var lhs: NSDecimal, var rhs: NSDecimal) -> Bool { | |
return NSDecimalCompare(&lhs, &rhs) == .OrderedAscending | |
} | |
extension NSDecimal: CustomStringConvertible { | |
public var description: String { | |
var printed = self | |
return NSDecimalString(&printed, nil) | |
} | |
} | |
extension NSDecimal: IntegerLiteralConvertible { | |
public init(integerLiteral value: IntegerLiteralType) { | |
self = NSDecimalNumber(integerLiteral: value).decimalValue | |
} | |
public init(_ value: Int) { | |
self = NSDecimalNumber(integer: value).decimalValue | |
} | |
public init(_ value: UInt) { | |
self = NSDecimalNumber(unsignedInteger: value).decimalValue | |
} | |
} | |
extension NSDecimal: FloatLiteralConvertible { | |
public init(floatLiteral value: FloatLiteralType) { | |
self = NSDecimalNumber(floatLiteral: value).decimalValue | |
} | |
public init(_ value: Float) { | |
self = NSDecimalNumber(float: value).decimalValue | |
} | |
public init(_ value: Double) { | |
self = NSDecimalNumber(double: value).decimalValue | |
} | |
} | |
public func +(var lhs: NSDecimal, var rhs: NSDecimal) -> NSDecimal { | |
var result = NSDecimal() | |
NSDecimalAdd(&result, &lhs, &rhs, .RoundPlain) | |
return result | |
} | |
public func *(var lhs: NSDecimal, var rhs: NSDecimal) -> NSDecimal { | |
var result = NSDecimal() | |
NSDecimalMultiply(&result, &lhs, &rhs, .RoundPlain) | |
return result | |
} | |
public func /(var lhs: NSDecimal, var rhs: NSDecimal) -> NSDecimal { | |
var result = NSDecimal() | |
NSDecimalDivide(&result, &lhs, &rhs, .RoundPlain) | |
return result | |
} | |
extension NSDecimal { | |
public var asDecimalNumber: NSDecimalNumber { return NSDecimalNumber(decimal: self) } | |
public var asInt: Int { return self.asDecimalNumber.integerValue } | |
public var asUInt: UInt { return self.asDecimalNumber.unsignedLongValue } | |
public var asDouble: Double { return self.asDecimalNumber.doubleValue } | |
} | |
extension NSDecimal { | |
func roundedWithScale(scale: Int, roundingMode: NSRoundingMode = .RoundPlain) -> NSDecimal { | |
var result = NSDecimal() | |
var rself = self | |
NSDecimalRound(&result, &rself, scale, roundingMode) | |
return result | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment