diff --git a/Source/SwiftyJSON/SwiftyJSON.swift b/Source/SwiftyJSON/SwiftyJSON.swift index f7a3f085..2b227933 100644 --- a/Source/SwiftyJSON/SwiftyJSON.swift +++ b/Source/SwiftyJSON/SwiftyJSON.swift @@ -67,14 +67,27 @@ JSON's type definitions. See http://www.json.org */ + +/// Options of JSON content +fileprivate enum Content { + case number(NSNumber) + case bool(Bool) + case string(String) + case null + case array([Any]) + case dictionary([String: Any]) + case unknown +} + +/// Type of JSON content public enum Type: Int { - case number - case string - case bool - case array - case dictionary - case null - case unknown + case number + case string + case bool + case array + case dictionary + case null + case unknown } // MARK: - JSON Base @@ -126,9 +139,21 @@ public struct JSON { if let data = jsonString.data(using: .utf8) { self.init(data) } else { - self.init(NSNull()) + self.init(jsonObject: NSNull()) } } + + /** + Creates a JSON using error parameter. + + - parameter error: The error to be linked to JSON. + + - returns: The created JSON with error. + */ + fileprivate init(error: SwiftyJSONError) { + self.content = .null + self.error = error + } /** Creates a JSON using the object. @@ -138,7 +163,7 @@ public struct JSON { - returns: The created JSON */ fileprivate init(jsonObject: Any) { - object = jsonObject + (self.content, self.error) = resolveContentAndError(for: jsonObject) } /** @@ -194,16 +219,21 @@ public struct JSON { } } - /// Private object - fileprivate var rawArray: [Any] = [] - fileprivate var rawDictionary: [String: Any] = [:] - fileprivate var rawString: String = "" - fileprivate var rawNumber: NSNumber = 0 - fileprivate var rawNull: NSNull = NSNull() - fileprivate var rawBool: Bool = false - - /// JSON type, fileprivate setter - public fileprivate(set) var type: Type = .null + /// JSON type + public var type: Type { + switch content { + case .array: return .array + case .bool: return .bool + case .dictionary: return .dictionary + case .null: return .null + case .number: return .number + case .string: return .string + case .unknown: return .unknown + } + } + + /// Content of JSON + fileprivate var content: Content /// Error in JSON, fileprivate setter public fileprivate(set) var error: SwiftyJSONError? @@ -211,50 +241,57 @@ public struct JSON { /// Object in JSON public var object: Any { get { - switch type { - case .array: return rawArray - case .dictionary: return rawDictionary - case .string: return rawString - case .number: return rawNumber - case .bool: return rawBool - default: return rawNull + switch content { + case .dictionary(let rawDictionary): return rawDictionary + case .array(let rawArray): return rawArray + case .string(let rawString): return rawString + case .number(let rawNumber): return rawNumber + case .bool(let rawBool): return rawBool + case .null, .unknown: return NSNull() } } set { - error = nil - switch unwrap(newValue) { - case let number as NSNumber: - if number.isBool { - type = .bool - rawBool = number.boolValue - } else { - type = .number - rawNumber = number - } - case let string as String: - type = .string - rawString = string - case _ as NSNull: - type = .null - case nil: - type = .null - case let array as [Any]: - type = .array - rawArray = array - case let dictionary as [String: Any]: - type = .dictionary - rawDictionary = dictionary - default: - type = .unknown - error = SwiftyJSONError.unsupportedType - } + (content, error) = resolveContentAndError(for: newValue) } } + + /// Mutating method that updates JSON's underlying content and error + private mutating func update(content newContent: Content, error: SwiftyJSONError?) { + self.content = newContent + self.error = error + } /// The static null JSON @available(*, unavailable, renamed:"null") public static var nullJSON: JSON { return null } - public static var null: JSON { return JSON(NSNull()) } + public static let null = JSON(NSNull()) +} + +/// Private method for resolving raw json object's content and error +private func resolveContentAndError(for jsonObject: Any) -> (Content, SwiftyJSONError?) { + let content = resolveContent(for: jsonObject) + let error: SwiftyJSONError? = (content == .unknown) ? .unsupportedType : nil + return (content, error) +} + +/// Private method for resolving raw json object's content +private func resolveContent(for jsonObject: Any) -> Content { + switch unwrap(jsonObject) { + case let dictionary as [String: Any]: + return .dictionary(dictionary) + case let array as [Any]: + return .array(array) + case nil: + return .null + case is NSNull: + return .null + case let string as String: + return .string(string) + case let number as NSNumber: + return number.isBool ? .bool(number.boolValue) : .number(number) + default: + return .unknown + } } /// Private method to unwarp an object recursively @@ -265,11 +302,7 @@ private func unwrap(_ object: Any) -> Any { case let array as [Any]: return array.map(unwrap) case let dictionary as [String: Any]: - var d = dictionary - dictionary.forEach { pair in - d[pair.key] = unwrap(pair.value) - } - return d + return dictionary.mapValues(unwrap) default: return object } @@ -296,6 +329,24 @@ public enum Index: Comparable { default: return false } } + + static public func <= (lhs: Index, rhs: Index) -> Bool { + switch (lhs, rhs) { + case (.array(let left), .array(let right)): return left <= right + case (.dictionary(let left), .dictionary(let right)): return left <= right + case (.null, .null): return true + default: return false + } + } + + static public func >= (lhs: Index, rhs: Index) -> Bool { + switch (lhs, rhs) { + case (.array(let left), .array(let right)): return left >= right + case (.dictionary(let left), .dictionary(let right)): return left >= right + case (.null, .null): return true + default: return false + } + } } public typealias JSONIndex = Index @@ -306,34 +357,44 @@ extension JSON: Swift.Collection { public typealias Index = JSONRawIndex public var startIndex: Index { - switch type { - case .array: return .array(rawArray.startIndex) - case .dictionary: return .dictionary(rawDictionary.startIndex) - default: return .null + switch content { + case .array(let rawArray): return .array(rawArray.startIndex) + case .dictionary(let rawDictionary): return .dictionary(rawDictionary.startIndex) + default: return .null } } public var endIndex: Index { - switch type { - case .array: return .array(rawArray.endIndex) - case .dictionary: return .dictionary(rawDictionary.endIndex) - default: return .null + switch content { + case .array(let rawArray): return .array(rawArray.endIndex) + case .dictionary(let rawDictionary): return .dictionary(rawDictionary.endIndex) + default: return .null } } public func index(after i: Index) -> Index { - switch i { - case .array(let idx): return .array(rawArray.index(after: idx)) - case .dictionary(let idx): return .dictionary(rawDictionary.index(after: idx)) - default: return .null + switch content { + case .array(let rawArray): + guard case .array(let idx) = i else { return .null } + return .array(rawArray.index(after: idx)) + case .dictionary(let rawDictionary): + guard case .dictionary(let idx) = i else { return .null } + return .dictionary(rawDictionary.index(after: idx)) + default: + return .null } } public subscript (position: Index) -> (String, JSON) { - switch position { - case .array(let idx): return (String(idx), JSON(rawArray[idx])) - case .dictionary(let idx): return (rawDictionary[idx].key, JSON(rawDictionary[idx].value)) - default: return ("", JSON.null) + switch content { + case .array(let rawArray): + guard case .array(let idx) = position else { return ("", JSON.null) } + return (String(idx), JSON(rawArray[idx])) + case .dictionary(let rawDictionary): + guard case .dictionary(let idx) = position else { return ("", JSON.null) } + return (rawDictionary[idx].key, JSON(rawDictionary[idx].value)) + default: + return ("", JSON.null) } } } @@ -369,46 +430,49 @@ extension JSON { /// If `type` is `.array`, return json whose object is `array[index]`, otherwise return null json with error. fileprivate subscript(index index: Int) -> JSON { get { - if type != .array { - var r = JSON.null - r.error = self.error ?? SwiftyJSONError.wrongType - return r - } else if rawArray.indices.contains(index) { - return JSON(rawArray[index]) - } else { - var r = JSON.null - r.error = SwiftyJSONError.indexOutOfBounds - return r + guard case .array(let rawArray) = content else { + return JSON(error: self.error ?? .wrongType) + } + guard rawArray.indices.contains(index) else { + return JSON(error: .indexOutOfBounds) } + return JSON(rawArray[index]) } set { - if type == .array && - rawArray.indices.contains(index) && - newValue.error == nil { - rawArray[index] = newValue.object + guard + case .array(let rawArray) = content, + rawArray.indices.contains(index), + newValue.error == nil + else { + return } + var copy = rawArray + copy[index] = newValue.object + content = .array(copy) } } /// If `type` is `.dictionary`, return json whose object is `dictionary[key]` , otherwise return null json with error. fileprivate subscript(key key: String) -> JSON { get { - var r = JSON.null - if type == .dictionary { - if let o = rawDictionary[key] { - r = JSON(o) - } else { - r.error = SwiftyJSONError.notExist - } - } else { - r.error = self.error ?? SwiftyJSONError.wrongType + guard case .dictionary(let rawDictionary) = content else { + return JSON(error: self.error ?? .wrongType) } - return r + guard let obj = rawDictionary[key] else { + return JSON(error: .notExist) + } + return JSON(obj) } set { - if type == .dictionary && newValue.error == nil { - rawDictionary[key] = newValue.object + guard + newValue.error == nil, + case .dictionary(let rawDictionary) = content + else { + return } + var copy = rawDictionary + copy[key] = newValue.object + content = .dictionary(copy) } } @@ -452,7 +516,7 @@ extension JSON { set { switch path.count { case 0: return - case 1: self[sub:path[0]].object = newValue.object + case 1: self[sub: path[0]].object = newValue.object default: var aPath = path aPath.remove(at: 0) @@ -541,11 +605,9 @@ extension JSON: Swift.ExpressibleByArrayLiteral { extension JSON: Swift.RawRepresentable { public init?(rawValue: Any) { - if JSON(rawValue).type == .unknown { - return nil - } else { - self.init(rawValue) - } + let json = JSON(rawValue) + guard json.type != .unknown else { return nil } + self = json } public var rawValue: Any { @@ -556,7 +618,6 @@ extension JSON: Swift.RawRepresentable { guard JSONSerialization.isValidJSONObject(object) else { throw SwiftyJSONError.invalidJSON } - return try JSONSerialization.data(withJSONObject: object, options: opt) } @@ -582,7 +643,7 @@ extension JSON: Swift.RawRepresentable { fileprivate func _rawString(_ encoding: String.Encoding = .utf8, options: [writingOptionsKeys: Any], maxObjectDepth: Int = 10) throws -> String? { guard maxObjectDepth > 0 else { throw SwiftyJSONError.invalidJSON } - switch type { + switch content { case .dictionary: do { if !(options[.castNilToNSNull] as? Bool ?? false) { @@ -648,11 +709,11 @@ extension JSON: Swift.RawRepresentable { } catch _ { return nil } - case .string: return rawString - case .number: return rawNumber.stringValue - case .bool: return rawBool.description - case .null: return "null" - default: return nil + case .string(let rawString): return rawString + case .number(let rawNumber): return rawNumber.stringValue + case .bool(let rawBool): return rawBool.description + case .null: return "null" + case .unknown: return nil } } } @@ -676,7 +737,8 @@ extension JSON { //Optional [JSON] public var array: [JSON]? { - return type == .array ? rawArray.map { JSON($0) } : nil + guard case .array(let rawArray) = content else { return nil } + return rawArray.map { JSON($0) } } //Non-optional [JSON] @@ -687,13 +749,17 @@ extension JSON { //Optional [Any] public var arrayObject: [Any]? { get { - switch type { - case .array: return rawArray - default: return nil + switch content { + case .array(let rawArray): return rawArray + default: return nil } } set { - self.object = newValue ?? NSNull() + if let newValue = newValue { + update(content: .array(newValue), error: nil) + } else { + update(content: .null, error: nil) + } } } } @@ -704,15 +770,8 @@ extension JSON { //Optional [String : JSON] public var dictionary: [String: JSON]? { - if type == .dictionary { - var d = [String: JSON](minimumCapacity: rawDictionary.count) - rawDictionary.forEach { pair in - d[pair.key] = JSON(pair.value) - } - return d - } else { - return nil - } + guard case .dictionary(let rawDictionary) = content else { return nil } + return rawDictionary.mapValues { JSON($0) } } //Non-optional [String : JSON] @@ -724,13 +783,15 @@ extension JSON { public var dictionaryObject: [String: Any]? { get { - switch type { - case .dictionary: return rawDictionary - default: return nil - } + guard case .dictionary(let rawDictionary) = content else { return nil } + return rawDictionary } set { - object = newValue ?? NSNull() + if let newValue = newValue { + update(content: .dictionary(newValue), error: nil) + } else { + update(content: .null, error: nil) + } } } } @@ -742,28 +803,32 @@ extension JSON { // : Swift.Bool //Optional bool public var bool: Bool? { get { - switch type { - case .bool: return rawBool - default: return nil + switch content { + case .bool(let rawBool): return rawBool + default: return nil } } set { - object = newValue ?? NSNull() + if let newValue = newValue { + update(content: .bool(newValue), error: nil) + } else { + update(content: .null, error: nil) + } } } //Non-optional bool public var boolValue: Bool { get { - switch type { - case .bool: return rawBool - case .number: return rawNumber.boolValue - case .string: return ["true", "y", "t", "yes", "1"].contains { rawString.caseInsensitiveCompare($0) == .orderedSame } - default: return false + switch content { + case .bool(let rawBool): return rawBool + case .number(let rawNumber): return rawNumber.boolValue + case .string(let rawString): return ["true", "y", "t", "yes", "1"].contains { rawString.caseInsensitiveCompare($0) == .orderedSame } + default: return false } } set { - object = newValue + update(content: .bool(newValue), error: nil) } } } @@ -775,28 +840,32 @@ extension JSON { //Optional string public var string: String? { get { - switch type { - case .string: return object as? String - default: return nil + switch content { + case .string(let rawString): return rawString + default: return nil } } set { - object = newValue ?? NSNull() + if let newValue = newValue { + update(content: .string(newValue), error: nil) + } else { + update(content: .null, error: nil) + } } } //Non-optional string public var stringValue: String { get { - switch type { - case .string: return object as? String ?? "" - case .number: return rawNumber.stringValue - case .bool: return (object as? Bool).map { String($0) } ?? "" - default: return "" + switch content { + case .string(let rawString): return rawString + case .number(let rawNumber): return rawNumber.stringValue + case .bool(let rawBool): return String(rawBool) + default: return "" } } set { - object = newValue + update(content: .string(newValue), error: nil) } } } @@ -808,31 +877,35 @@ extension JSON { //Optional number public var number: NSNumber? { get { - switch type { - case .number: return rawNumber - case .bool: return NSNumber(value: rawBool ? 1 : 0) - default: return nil + switch content { + case .number(let rawNumber): return rawNumber + case .bool(let rawBool): return NSNumber(value: rawBool ? 1 : 0) + default: return nil } } set { - object = newValue ?? NSNull() + if let newValue = newValue { + update(content: .number(newValue), error: nil) + } else { + update(content: .null, error: nil) + } } } //Non-optional number public var numberValue: NSNumber { get { - switch type { - case .string: - let decimal = NSDecimalNumber(string: object as? String) + switch content { + case .string(let rawString): + let decimal = NSDecimalNumber(string: rawString) return decimal == .notANumber ? .zero : decimal - case .number: return object as? NSNumber ?? NSNumber(value: 0) - case .bool: return NSNumber(value: rawBool ? 1 : 0) - default: return NSNumber(value: 0.0) + case .number(let rawNumber): return rawNumber + case .bool(let rawBool): return NSNumber(value: rawBool ? 1 : 0) + default: return NSNumber(value: 0.0) } } set { - object = newValue + update(content: .number(newValue), error: nil) } } } @@ -843,11 +916,11 @@ extension JSON { public var null: NSNull? { set { - object = NSNull() + update(content: .null, error: nil) } get { - switch type { - case .null: return rawNull + switch content { + case .null: return NSNull() default: return nil } } @@ -867,8 +940,8 @@ extension JSON { //Optional URL public var url: URL? { get { - switch type { - case .string: + switch content { + case .string(let rawString): // Check for existing percent escapes first to prevent double-escaping of % character if rawString.range(of: "%[0-9A-Fa-f]{2}", options: .regularExpression, range: nil, locale: nil) != nil { return Foundation.URL(string: rawString) @@ -883,7 +956,11 @@ extension JSON { } } set { - object = newValue?.absoluteString ?? NSNull() + if let newValue = newValue { + update(content: .string(newValue.absoluteString), error: nil) + } else { + update(content: .null, error: nil) + } } } } @@ -897,11 +974,7 @@ extension JSON { return number?.doubleValue } set { - if let newValue = newValue { - object = NSNumber(value: newValue) - } else { - object = NSNull() - } + number = newValue.map { NSNumber(value: $0) } } } @@ -910,7 +983,7 @@ extension JSON { return numberValue.doubleValue } set { - object = NSNumber(value: newValue) + numberValue = NSNumber(value: newValue) } } @@ -919,11 +992,7 @@ extension JSON { return number?.floatValue } set { - if let newValue = newValue { - object = NSNumber(value: newValue) - } else { - object = NSNull() - } + number = newValue.map { NSNumber(value: $0) } } } @@ -932,7 +1001,7 @@ extension JSON { return numberValue.floatValue } set { - object = NSNumber(value: newValue) + numberValue = NSNumber(value: newValue) } } @@ -941,11 +1010,7 @@ extension JSON { return number?.intValue } set { - if let newValue = newValue { - object = NSNumber(value: newValue) - } else { - object = NSNull() - } + number = newValue.map { NSNumber(value: $0) } } } @@ -954,7 +1019,7 @@ extension JSON { return numberValue.intValue } set { - object = NSNumber(value: newValue) + numberValue = NSNumber(value: newValue) } } @@ -963,11 +1028,7 @@ extension JSON { return number?.uintValue } set { - if let newValue = newValue { - object = NSNumber(value: newValue) - } else { - object = NSNull() - } + number = newValue.map { NSNumber(value: $0) } } } @@ -976,7 +1037,7 @@ extension JSON { return numberValue.uintValue } set { - object = NSNumber(value: newValue) + numberValue = NSNumber(value: newValue) } } @@ -985,11 +1046,7 @@ extension JSON { return number?.int8Value } set { - if let newValue = newValue { - object = NSNumber(value: Int(newValue)) - } else { - object = NSNull() - } + number = newValue.map { NSNumber(value: Int($0)) } } } @@ -998,7 +1055,7 @@ extension JSON { return numberValue.int8Value } set { - object = NSNumber(value: Int(newValue)) + numberValue = NSNumber(value: Int(newValue)) } } @@ -1007,11 +1064,7 @@ extension JSON { return number?.uint8Value } set { - if let newValue = newValue { - object = NSNumber(value: newValue) - } else { - object = NSNull() - } + number = newValue.map { NSNumber(value: $0) } } } @@ -1020,7 +1073,7 @@ extension JSON { return numberValue.uint8Value } set { - object = NSNumber(value: newValue) + numberValue = NSNumber(value: newValue) } } @@ -1029,11 +1082,7 @@ extension JSON { return number?.int16Value } set { - if let newValue = newValue { - object = NSNumber(value: newValue) - } else { - object = NSNull() - } + number = newValue.map { NSNumber(value: $0) } } } @@ -1042,7 +1091,7 @@ extension JSON { return numberValue.int16Value } set { - object = NSNumber(value: newValue) + numberValue = NSNumber(value: newValue) } } @@ -1051,11 +1100,7 @@ extension JSON { return number?.uint16Value } set { - if let newValue = newValue { - object = NSNumber(value: newValue) - } else { - object = NSNull() - } + number = newValue.map { NSNumber(value: $0) } } } @@ -1064,7 +1109,7 @@ extension JSON { return numberValue.uint16Value } set { - object = NSNumber(value: newValue) + numberValue = NSNumber(value: newValue) } } @@ -1073,11 +1118,7 @@ extension JSON { return number?.int32Value } set { - if let newValue = newValue { - object = NSNumber(value: newValue) - } else { - object = NSNull() - } + number = newValue.map { NSNumber(value: $0) } } } @@ -1086,7 +1127,7 @@ extension JSON { return numberValue.int32Value } set { - object = NSNumber(value: newValue) + numberValue = NSNumber(value: newValue) } } @@ -1095,11 +1136,7 @@ extension JSON { return number?.uint32Value } set { - if let newValue = newValue { - object = NSNumber(value: newValue) - } else { - object = NSNull() - } + number = newValue.map { NSNumber(value: $0) } } } @@ -1108,7 +1145,7 @@ extension JSON { return numberValue.uint32Value } set { - object = NSNumber(value: newValue) + numberValue = NSNumber(value: newValue) } } @@ -1117,11 +1154,7 @@ extension JSON { return number?.int64Value } set { - if let newValue = newValue { - object = NSNumber(value: newValue) - } else { - object = NSNull() - } + number = newValue.map { NSNumber(value: $0) } } } @@ -1130,7 +1163,7 @@ extension JSON { return numberValue.int64Value } set { - object = NSNumber(value: newValue) + numberValue = NSNumber(value: newValue) } } @@ -1139,11 +1172,7 @@ extension JSON { return number?.uint64Value } set { - if let newValue = newValue { - object = NSNumber(value: newValue) - } else { - object = NSNull() - } + number = newValue.map { NSNumber(value: $0) } } } @@ -1152,7 +1181,68 @@ extension JSON { return numberValue.uint64Value } set { - object = NSNumber(value: newValue) + numberValue = NSNumber(value: newValue) + } + } +} +// MARK: - Content: Comparable + +extension Content: Comparable { + + static func == (lhs: Content, rhs: Content) -> Bool { + + switch (lhs, rhs) { + case let (.number(l), .number(r)): return l == r + case let (.string(l), .string(r)): return l == r + case let (.bool(l), .bool(r)): return l == r + case let (.array(l), .array(r)): return l as NSArray == r as NSArray + case let (.dictionary(l), .dictionary(r)): return l as NSDictionary == r as NSDictionary + case (.null, .null): return true + default: return false + } + } + + static func <= (lhs: Content, rhs: Content) -> Bool { + + switch (lhs, rhs) { + case let (.number(l), .number(r)): return l <= r + case let (.string(l), .string(r)): return l <= r + case let (.bool(l), .bool(r)): return l == r + case let (.array(l), .array(r)): return l as NSArray == r as NSArray + case let (.dictionary(l), .dictionary(r)): return l as NSDictionary == r as NSDictionary + case (.null, .null): return true + default: return false + } + } + + static func >= (lhs: Content, rhs: Content) -> Bool { + + switch (lhs, rhs) { + case let (.number(l), .number(r)): return l >= r + case let (.string(l), .string(r)): return l >= r + case let (.bool(l), .bool(r)): return l == r + case let (.array(l), .array(r)): return l as NSArray == r as NSArray + case let (.dictionary(l), .dictionary(r)): return l as NSDictionary == r as NSDictionary + case (.null, .null): return true + default: return false + } + } + + static func > (lhs: Content, rhs: Content) -> Bool { + + switch (lhs, rhs) { + case let (.number(l), .number(r)): return l > r + case let (.string(l), .string(r)): return l > r + default: return false + } + } + + static func < (lhs: Content, rhs: Content) -> Bool { + + switch (lhs, rhs) { + case let (.number(l), .number(r)): return l < r + case let (.string(l), .string(r)): return l < r + default: return false } } } @@ -1162,60 +1252,23 @@ extension JSON { extension JSON: Swift.Comparable {} public func == (lhs: JSON, rhs: JSON) -> Bool { - - switch (lhs.type, rhs.type) { - case (.number, .number): return lhs.rawNumber == rhs.rawNumber - case (.string, .string): return lhs.rawString == rhs.rawString - case (.bool, .bool): return lhs.rawBool == rhs.rawBool - case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray - case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary - case (.null, .null): return true - default: return false - } + return lhs.content == rhs.content } public func <= (lhs: JSON, rhs: JSON) -> Bool { - - switch (lhs.type, rhs.type) { - case (.number, .number): return lhs.rawNumber <= rhs.rawNumber - case (.string, .string): return lhs.rawString <= rhs.rawString - case (.bool, .bool): return lhs.rawBool == rhs.rawBool - case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray - case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary - case (.null, .null): return true - default: return false - } + return lhs.content <= rhs.content } public func >= (lhs: JSON, rhs: JSON) -> Bool { - - switch (lhs.type, rhs.type) { - case (.number, .number): return lhs.rawNumber >= rhs.rawNumber - case (.string, .string): return lhs.rawString >= rhs.rawString - case (.bool, .bool): return lhs.rawBool == rhs.rawBool - case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray - case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary - case (.null, .null): return true - default: return false - } + return lhs.content >= rhs.content } public func > (lhs: JSON, rhs: JSON) -> Bool { - - switch (lhs.type, rhs.type) { - case (.number, .number): return lhs.rawNumber > rhs.rawNumber - case (.string, .string): return lhs.rawString > rhs.rawString - default: return false - } + return lhs.content > rhs.content } public func < (lhs: JSON, rhs: JSON) -> Bool { - - switch (lhs.type, rhs.type) { - case (.number, .number): return lhs.rawNumber < rhs.rawNumber - case (.string, .string): return lhs.rawString < rhs.rawString - default: return false - } + return lhs.content < rhs.content } private let trueNumber = NSNumber(value: true) @@ -1227,9 +1280,12 @@ private let falseObjCType = String(cString: falseNumber.objCType) extension NSNumber { fileprivate var isBool: Bool { - let objCType = String(cString: self.objCType) - if (self.compare(trueNumber) == .orderedSame && objCType == trueObjCType) || (self.compare(falseNumber) == .orderedSame && objCType == falseObjCType) { - return true + if self.compare(trueNumber) == .orderedSame { + let objCType = String(cString: self.objCType) + return objCType == trueObjCType + } else if self.compare(falseNumber) == .orderedSame { + let objCType = String(cString: self.objCType) + return objCType == falseObjCType } else { return false } diff --git a/Tests/SwiftJSONTests/PerformanceTests.swift b/Tests/SwiftJSONTests/PerformanceTests.swift index 7535f7d8..8c16c3fa 100644 --- a/Tests/SwiftJSONTests/PerformanceTests.swift +++ b/Tests/SwiftJSONTests/PerformanceTests.swift @@ -60,7 +60,7 @@ class PerformanceTests: XCTestCase { return } self.measure { - for _ in 1...100 { + for _ in 1...100_000 { let object: Any? = json.object XCTAssertTrue(object != nil) } @@ -73,7 +73,7 @@ class PerformanceTests: XCTestCase { return } self.measure { - for _ in 1...100 { + for _ in 1...1_000 { autoreleasepool { if let array = json.array { XCTAssertTrue(array.count > 0) @@ -89,7 +89,7 @@ class PerformanceTests: XCTestCase { return } self.measure { - for _ in 1...100 { + for _ in 1...1_000 { autoreleasepool { if let dictionary = json.dictionary { XCTAssertTrue(dictionary.count > 0) @@ -116,7 +116,7 @@ class PerformanceTests: XCTestCase { func testLargeDictionaryMethodPerformance() { var data: [String: JSON] = [:] - (0...100000).forEach { n in + (0...100_000).forEach { n in data["\(n)"] = JSON([ "name": "item\(n)", "id": n @@ -127,11 +127,33 @@ class PerformanceTests: XCTestCase { self.measure { autoreleasepool { if let dictionary = json.dictionary { - XCTAssertTrue(dictionary.count == 100001) + XCTAssertTrue(dictionary.count == 100_001) } else { XCTFail("dictionary should not be nil") } } } } + + func testLongPathDictionaryLookupPerformance() { + + let dictionary: JSON = [ + "one": [ + "two": [ + "three": [ + "four": [ + "five": "result" + ] + ] + ] + ] + ] + + self.measure { + (1...10_000).forEach { _ in + let resultValue = dictionary["one"]["two"]["three"]["four"]["five"] + XCTAssertTrue(resultValue == "result") + } + } + } }