diff --git a/FxJSON.playground/Pages/Using Protocols.xcplaygroundpage/Contents.swift b/FxJSON.playground/Pages/Using Protocols.xcplaygroundpage/Contents.swift index 4c10589..e75a205 100644 --- a/FxJSON.playground/Pages/Using Protocols.xcplaygroundpage/Contents.swift +++ b/FxJSON.playground/Pages/Using Protocols.xcplaygroundpage/Contents.swift @@ -65,7 +65,7 @@ class BasicClass: JSONDecodable, JSONEncodable { signUpTime = try json["signUpTime"]< } - func encode(mapper: JSON.Mapper) { + func encode(mapper: JSON.Wrapper) { mapper["userID"] << userID mapper["name"] << name mapper["admin"] << admin @@ -85,7 +85,7 @@ class UserClass: BasicClass { try super.init(decode: json) } - override func encode(mapper: JSON.Mapper) { + override func encode(mapper: JSON.Wrapper) { mapper[ignoreIfNull: "website"] << website mapper["friends"] << friends super.encode(mapper: mapper) diff --git a/FxJSON.xcodeproj/project.pbxproj b/FxJSON.xcodeproj/project.pbxproj index d2a78a5..8af5ea9 100755 --- a/FxJSON.xcodeproj/project.pbxproj +++ b/FxJSON.xcodeproj/project.pbxproj @@ -16,8 +16,9 @@ F323F5EB1DA8DB87009D824E /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = F323F5E61DA8DB87009D824E /* JSON.swift */; }; F323F5EE1DA8DB87009D824E /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = F323F5E81DA8DB87009D824E /* Protocols.swift */; }; F323F5EF1DA8DB87009D824E /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = F323F5E81DA8DB87009D824E /* Protocols.swift */; }; - F323F5F01DA8DB87009D824E /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F323F5E91DA8DB87009D824E /* Collection.swift */; }; - F323F5F11DA8DB87009D824E /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F323F5E91DA8DB87009D824E /* Collection.swift */; }; + F323F5F01DA8DB87009D824E /* Subscript.swift in Sources */ = {isa = PBXBuildFile; fileRef = F323F5E91DA8DB87009D824E /* Subscript.swift */; }; + F323F5F11DA8DB87009D824E /* Subscript.swift in Sources */ = {isa = PBXBuildFile; fileRef = F323F5E91DA8DB87009D824E /* Subscript.swift */; }; + F356E77E20D659CB00B4B9C1 /* JSONWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F356E77D20D659CB00B4B9C1 /* JSONWrapper.swift */; }; F371ED181DAF826A005EE24D /* MapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F371ED171DAF826A005EE24D /* MapperTests.swift */; }; F39DBD401DAE24E70055ED01 /* Implements.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39DBD3F1DAE24E70055ED01 /* Implements.swift */; }; F39DBD411DAE24E70055ED01 /* Implements.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39DBD3F1DAE24E70055ED01 /* Implements.swift */; }; @@ -45,13 +46,14 @@ F323F5D01DA8DB31009D824E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F323F5D51DA8DB32009D824E /* FxJSONTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FxJSONTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F323F5DC1DA8DB32009D824E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - F323F5E61DA8DB87009D824E /* JSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; - F323F5E81DA8DB87009D824E /* Protocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Protocols.swift; sourceTree = ""; }; - F323F5E91DA8DB87009D824E /* Collection.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = ""; tabWidth = 2; wrapsLines = 1; }; + F323F5E61DA8DB87009D824E /* JSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; tabWidth = 2; }; + F323F5E81DA8DB87009D824E /* Protocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Protocols.swift; sourceTree = ""; tabWidth = 2; }; + F323F5E91DA8DB87009D824E /* Subscript.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Subscript.swift; sourceTree = ""; tabWidth = 2; wrapsLines = 1; }; + F356E77D20D659CB00B4B9C1 /* JSONWrapper.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = JSONWrapper.swift; sourceTree = ""; tabWidth = 2; }; F371ED171DAF826A005EE24D /* MapperTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapperTests.swift; sourceTree = ""; }; - F39DBD3F1DAE24E70055ED01 /* Implements.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Implements.swift; sourceTree = ""; }; + F39DBD3F1DAE24E70055ED01 /* Implements.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Implements.swift; sourceTree = ""; tabWidth = 2; }; F3C1289C1DBB9AFD001EA4B3 /* ProtocolTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProtocolTests.swift; sourceTree = ""; }; - F3C1289E1DBDD600001EA4B3 /* PerformanceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PerformanceTests.swift; sourceTree = ""; }; + F3C1289E1DBDD600001EA4B3 /* PerformanceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = PerformanceTests.swift; sourceTree = ""; tabWidth = 2; }; F3C1289F1DBDD600001EA4B3 /* SerializeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SerializeTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -96,7 +98,8 @@ isa = PBXGroup; children = ( F323F5E61DA8DB87009D824E /* JSON.swift */, - F323F5E91DA8DB87009D824E /* Collection.swift */, + F356E77D20D659CB00B4B9C1 /* JSONWrapper.swift */, + F323F5E91DA8DB87009D824E /* Subscript.swift */, F323F5E81DA8DB87009D824E /* Protocols.swift */, F39DBD3F1DAE24E70055ED01 /* Implements.swift */, F323F5CF1DA8DB31009D824E /* FxJSON.h */, @@ -177,17 +180,18 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0800; - LastUpgradeCheck = 0810; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = com.frain; TargetAttributes = { F323F5CB1DA8DB31009D824E = { CreatedOnToolsVersion = 8.0; - LastSwiftMigration = 0800; + LastSwiftMigration = 1000; ProvisioningStyle = Automatic; }; F323F5D41DA8DB32009D824E = { CreatedOnToolsVersion = 8.0; - DevelopmentTeam = NYY93G8LZ7; + DevelopmentTeam = 38U8W94Y3Q; + LastSwiftMigration = 1000; ProvisioningStyle = Automatic; }; }; @@ -234,7 +238,8 @@ buildActionMask = 2147483647; files = ( F323F5EA1DA8DB87009D824E /* JSON.swift in Sources */, - F323F5F01DA8DB87009D824E /* Collection.swift in Sources */, + F323F5F01DA8DB87009D824E /* Subscript.swift in Sources */, + F356E77E20D659CB00B4B9C1 /* JSONWrapper.swift in Sources */, F39DBD401DAE24E70055ED01 /* Implements.swift in Sources */, F323F5EE1DA8DB87009D824E /* Protocols.swift in Sources */, ); @@ -247,7 +252,7 @@ F31A4D141DACDE6C0001FB1B /* InitTests.swift in Sources */, F323F5EB1DA8DB87009D824E /* JSON.swift in Sources */, F39DBD411DAE24E70055ED01 /* Implements.swift in Sources */, - F323F5F11DA8DB87009D824E /* Collection.swift in Sources */, + F323F5F11DA8DB87009D824E /* Subscript.swift in Sources */, F323F5EF1DA8DB87009D824E /* Protocols.swift in Sources */, F31A4D161DACE0DD0001FB1B /* SubscriptTests.swift in Sources */, F3C128A11DBDD600001EA4B3 /* SerializeTests.swift in Sources */, @@ -277,15 +282,23 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -331,15 +344,23 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -388,7 +409,8 @@ SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -410,7 +432,8 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -418,12 +441,13 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - DEVELOPMENT_TEAM = NYY93G8LZ7; + DEVELOPMENT_TEAM = 38U8W94Y3Q; INFOPLIST_FILE = FxJSONTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.frain.FxJSONTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -431,12 +455,13 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - DEVELOPMENT_TEAM = NYY93G8LZ7; + DEVELOPMENT_TEAM = 38U8W94Y3Q; INFOPLIST_FILE = FxJSONTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.frain.FxJSONTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/FxJSON.xcodeproj/xcshareddata/xcschemes/FxJSON.xcscheme b/FxJSON.xcodeproj/xcshareddata/xcschemes/FxJSON.xcscheme index 3bbd54e..c0f5ed8 100644 --- a/FxJSON.xcodeproj/xcshareddata/xcschemes/FxJSON.xcscheme +++ b/FxJSON.xcodeproj/xcshareddata/xcschemes/FxJSON.xcscheme @@ -1,6 +1,6 @@ + + diff --git a/FxJSON/Collection.swift b/FxJSON/Collection.swift deleted file mode 100755 index 78eb8f2..0000000 --- a/FxJSON/Collection.swift +++ /dev/null @@ -1,350 +0,0 @@ -// -// Collection.swift -// FxJSON -// -// Created by Frain on 7/2/16. -// -// The MIT License (MIT) -// -// Copyright (c) 2016~2017 Frain -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -import Foundation - -extension JSON: Collection { - - public enum Index: Comparable { - - public enum Key: CustomStringConvertible { - - case index(DictionaryIndex) - case key(String) - - public var description: String { - switch self { - case .index(let v): return "\(v)" - case .key(let v): return v - } - } - } - - case key(Key) - case index(Int) - case path([JSON.Index]) - } - - public var startIndex: JSON.Index { - switch self { - case let .object(dict): - return .key(.index(dict.startIndex)) - case let .array(arr): - return .index(arr.startIndex) - default: - return .path([]) - } - } - - public var endIndex: JSON.Index { - switch self { - case let .object(dict): - return .key(.index(dict.endIndex)) - case let .array(arr): - return .index(arr.endIndex) - default: - return .path([]) - } - } - - public func index(after i: JSON.Index) -> JSON.Index { - switch (i, self) { - case let (.key(.index(v)), .object(dict)): - return .key(.index(dict.index(after: v))) - case let (.index(v), .array(arr)): - return .index(arr.index(after: v)) - default: - return .path([]) - } - } -} - -public extension JSON { - - subscript(path: JSON.Index...) -> JSON { - get { - return self[path] - } - set { - self[path] = newValue - } - } - - subscript(index: JSON.Index) -> JSON { - get { - switch index { - case let .key(v): return self[v] - case let .index(v): return self[v] - case let .path(v): return self[v] - } - } - set { - switch index { - case let .key(v): self[v] = newValue - case let .index(v): self[v] = newValue - case let .path(v): self[v] = newValue - } - } - } - - subscript(transform: Transform) -> JSON { - get { - guard !isError, let from = transform.fromJSONFunc else { return self } - return JSON.init(try from(transform.jsonObjectType.init(decode: self))) - } - set { - guard !newValue.isError, let to = transform.toJSONFunc else { self = newValue; return } - self = JSON.init(try to(transform.objectType.init(decode: newValue))) - } - } -} - -extension JSON { - - subscript(ignoreIfNull path: [JSON.Index]) -> JSON { - get { - return self[create: path] - } - set { - if newValue.isNull { return } - self[create: path] = newValue - } - } - - subscript(create index: JSON.Index) -> JSON { - get { - switch index { - case let .key(v): return self[create: v] - case let .index(v): return self[create: v] - case let .path(v): return self[create: v] - } - } - set { - switch index { - case let .key(v): self[create: v] = newValue - case let .index(v): self[create: v] = newValue - case let .path(v): self[create: v] = newValue - } - } - } - - subscript(path: [JSON.Index]) -> JSON { - get { - return path.reduce(self) { $0[$1] } - } - set { - guard !path.isEmpty else { self = newValue; return } - var path = path - let first = path.remove(at: 0) - self[first][path] = newValue - } - } - - subscript(create path: [JSON.Index]) -> JSON { - get { - return path.reduce(self) { $0[create: $1] } - } - set { - guard !path.isEmpty else { self = newValue; return } - var path = path - let first = path.remove(at: 0) - self[create: first][create: path] = newValue - } - } - - subscript(key: Index.Key) -> JSON { - get { - switch (self, key) { - case let (.object(dict), .key(key)): - if let o = dict[key] { return JSON(any: o) } - return .error(Error.notExist(dict: dict, key: key)) - case let (.object(dict), .index(index)): - return JSON(any: dict[index].value) - case (.error, _): - return self - default: - return .error(Error.wrongType(subscript: self, key: .key(key))) - } - } - set { - if case var .object(dict) = self { - switch key { - case let .key(key): dict[key] = newValue.object - case let .index(index): dict[dict[index].key] = newValue.object - } - self = .object(dict) - } - } - } - - subscript(create key: Index.Key) -> JSON { - get { - switch (self, key) { - case let (.object(dict), .key(key)): - guard let any = dict[key] else { return JSON() } - return JSON(any: any) - case let (.object(dict), .index(index)): - return JSON(any: dict[index].value) - case (.error, _): - return self - default: - return JSON() - } - } - set { - if case var .object(dict) = self { - switch key { - case let .key(key): dict[key] = newValue.object - case let .index(key): dict[dict[key].key] = newValue.object - } - self = .object(dict) - } else if case let .key(key) = key { - self = .object([key: newValue.object]) - } - } - } - - subscript(index: Int) -> JSON { - get { - switch self { - case let .array(arr): - if 0.. JSON { - get { - switch self { - case let .array(arr) where index < arr.count && index >= 0: - return JSON(any: arr[index]) - case .error: - return self - default: - return JSON() - } - } - set { - switch self { - case var .array(arr) where index >= 0: - if index >= arr.count { - for _ in 0...index - arr.count { arr.append(NSNull()) } - } - arr[index] = newValue.object - self = .array(arr) - case _ where index >= 0: - var arr = [Any](repeating: NSNull(), count: index + 1) - arr[index] = newValue.object - self = .array(arr) - default: - return - } - } - } -} - -//MARK: JSON.Index extension - -extension JSON.Index: ExpressibleByArrayLiteral { - - public init(arrayLiteral elements: JSON.Index...) { - self = .path(elements) - } -} - -extension JSON.Index: ExpressibleByStringLiteral { - - public init(stringLiteral value: String) { - self = value.isEmpty ? .path([]) : .key(.key(value)) - } - - public init(unicodeScalarLiteral value: String) { - self = value.isEmpty ? .path([]) : .key(.key(value)) - } - - public init(extendedGraphemeClusterLiteral value: String) { - self = value.isEmpty ? .path([]) : .key(.key(value)) - } -} - -extension JSON.Index: ExpressibleByIntegerLiteral { - - public init(integerLiteral value: Int) { - self = .index(value) - } -} - -extension JSON.Index: CustomStringConvertible { - - public var description: String { - switch self { - case let .index(v): return v.description - case let .key(v): return "\"\(v)\"" - case let .path(v): return v.description - } - } -} - -public func ==(lhs: JSON.Index, rhs: JSON.Index) -> Bool { - switch (lhs, rhs) { - case let (.key(.key(l)), .key(.key(r))): - return l == r - case let (.key(.index(l)), .key(.index(r))): - return l == r - case let (.index(l), .index(r)): - return l == r - case let (.path(l), .path(r)): - return l == r - default: - return false - } -} - -public func <(lhs: JSON.Index, rhs: JSON.Index) -> Bool { - switch (lhs, rhs) { - case let (.key(.key(l)), .key(.key(r))): - return l < r - case let (.key(.index(l)), .key(.index(r))): - return l < r - case let (.index(l), .index(r)): - return l < r - default: - return false - } -} diff --git a/FxJSON/Implements.swift b/FxJSON/Implements.swift index dd8de30..7cc8c29 100755 --- a/FxJSON/Implements.swift +++ b/FxJSON/Implements.swift @@ -30,40 +30,39 @@ import Foundation //MARK: - JSON -extension JSON: JSONEncodable { - - public var json: JSON { - return self - } +extension JSON { public init(_ object: @autoclosure () throws -> JSONEncodable) { do { try self = object().json } catch { self = .error(error) } } - public init(operate: (Mapper) -> ()) { - let mapper = Mapper(json: .object([:])) - operate(mapper) - self = mapper.json + public init(operate: (Wrapper) -> ()) { + let wrapper = wrap(.object([:])) + operate(wrapper) + self = wrapper.json } +} + +extension JSON.Wrapper { - public func transformed(operate: (Mapper) -> ()) -> JSON { - let mapper = Mapper(json: self) - operate(mapper) - return(mapper.json) + public func transformed(operate: (JSON.Wrapper) -> ()) -> JSON.Wrapper { + let wrapper = self + operate(wrapper) + return wrapper } public func decode() throws -> T { - return try T(decode: self) + return try T(decode: json) } public func map( - _ transform: (T) throws -> U) rethrows -> JSON { - return try T(self).map(transform).map { $0.json } ?? self + _ transform: (T) throws -> U) rethrows -> JSON.Wrapper { + return try T(json).map(transform).map { $0.json }.map(wrap) ?? self } public func flatMap( - _ transform: (T) throws -> JSON) rethrows -> JSON { - return try T(self).map(transform) ?? self + _ transform: (T) throws -> JSON) rethrows -> JSON.Wrapper { + return try T(json).map(transform).map(wrap) ?? self } } @@ -96,84 +95,84 @@ extension Bool: JSONCodable, DefaultInitable { extension Float: JSONCodable, DefaultInitable { public init(decode json: JSON) throws { - guard case let .number(num) = json else { throw Float.mismatchError(json: json) } - self = num.floatValue + guard case let .number(.double(num)) = json else { throw Float.mismatchError(json: json) } + self = Float(num) } public var json: JSON { - return .number(NSNumber(value: self)) + return .number(.double(Double(self))) } } extension Double: JSONCodable, DefaultInitable { public init(decode json: JSON) throws { - guard case let .number(num) = json else { throw Double.mismatchError(json: json) } - self = num.doubleValue + guard case let .number(.double(num)) = json else { throw Double.mismatchError(json: json) } + self = num } public var json: JSON { - return .number(NSNumber(value: self)) + return .number(.double(self)) } } extension Int: JSONCodable, DefaultInitable { public init(decode json: JSON) throws { - guard case let .number(num) = json else { throw Int.mismatchError(json: json) } - self = num.intValue + guard case let .number(.int(num)) = json else { throw Int.mismatchError(json: json) } + self = num } public var json: JSON { - return .number(NSNumber(value: self)) + return .number(.int(self)) } } extension Int8: JSONCodable, DefaultInitable { public init(decode json: JSON) throws { - guard case let .number(num) = json else { throw Int8.mismatchError(json: json) } - self = num.int8Value + guard case let .number(.int(num)) = json else { throw Int8.mismatchError(json: json) } + self.init(num) } public var json: JSON { - return .number(NSNumber(value: self)) + return .number(.int(Int(self))) } } extension Int16: JSONCodable, DefaultInitable { public init(decode json: JSON) throws { - guard case let .number(num) = json else { throw Int16.mismatchError(json: json) } - self = num.int16Value + guard case let .number(.int(num)) = json else { throw Int16.mismatchError(json: json) } + self.init(num) } public var json: JSON { - return .number(NSNumber(value: self)) + return .number(.int(Int(self))) } } extension Int32: JSONCodable, DefaultInitable { public init(decode json: JSON) throws { - guard case let .number(num) = json else { throw Int32.mismatchError(json: json) } - self = num.int32Value + guard case let .number(.int(num)) = json else { throw Int32.mismatchError(json: json) } + self.init(num) } public var json: JSON { - return .number(NSNumber(value: self)) + return .number(.int(Int(self))) } } extension Int64: JSONCodable, DefaultInitable { public init(decode json: JSON) throws { - guard case let .number(num) = json else { throw Int64.mismatchError(json: json) } - self = num.int64Value + guard case let .number(.int(num)) = json else { throw Int64.mismatchError(json: json) } + self.init(num) } public var json: JSON { - return .number(NSNumber(value: self)) + return .number(.int(Int(self))) } } @@ -198,14 +197,14 @@ extension Date: JSONCodable { throw JSON.Error.formatter(format: formatter.dateFormat, value: dateString) } self = date - case .number(let v): + case .number(.double(let v)): let since: DateTransform.Since = { if case .timeIntervalSince(let since) = DateTransform.default { return since } return .default }() - self.init(timeIntervalSince1970: v.doubleValue + since.timeInterval) + self.init(timeIntervalSince1970: v + since.timeInterval) default: throw DateTransform.default.objectType.mismatchError(json: json) } @@ -216,7 +215,7 @@ extension Date: JSONCodable { case .formatter(let formatetr): return .string(formatetr.string(from: self)) case .timeIntervalSince(let since): - return .number(NSNumber(value: timeIntervalSince1970 - since.timeInterval)) + return .number(.double(timeIntervalSince1970 - since.timeInterval)) } } } @@ -227,7 +226,7 @@ extension URL: JSONCodable { guard let urlString = String(json)? .addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) else { throw String.mismatchError(json: json) } - if urlString.characters.isEmpty, let url = URL(string: "about://") { self = url; return } + if urlString.isEmpty, let url = URL(string: "about://") { self = url; return } guard let url = URL(string: urlString) else { throw JSON.Error.other(description: "URL init error, urlString is \(urlString)") } @@ -269,32 +268,7 @@ extension Optional: JSONCodable { } } -extension ImplicitlyUnwrappedOptional: JSONCodable, DefaultInitable { - - public init?(_ json: JSON) { - guard let dic = (try? ImplicitlyUnwrappedOptional(decode: json)) else { return nil } - self = dic - } - - public init(decode json: JSON) throws { - guard let T = Wrapped.self as? JSONDecodable.Type else { - throw JSON.Error.notConfirmTo(protocol: JSONDecodable.self, actual: Wrapped.self) - } - self = T.init(json) as! Wrapped? - } - - public var json: JSON { - guard Wrapped.self is JSONEncodable.Type else { - return .error(JSON.Error.notConfirmTo(protocol: JSONEncodable.self, actual: Wrapped.self)) - } - switch self { - case let .some(value as JSONEncodable): return value.json - default: return nil - } - } -} - -extension Set: JSONCodable, DefaultInitable { +extension Set: JSONDecodable where Element: JSONDecodable { public init?(_ json: JSON) { guard let dic = (try? Set(decode: json)) else { return nil } @@ -303,113 +277,82 @@ extension Set: JSONCodable, DefaultInitable { public init(decode json: JSON) throws { self.init() - guard let T = Element.self as? JSONDecodable.Type else { - throw JSON.Error.notConfirmTo(protocol: JSONDecodable.self, actual: Element.self) - } - for value in json.asArray { - if let value = T.init(value) as! Element? { + for value in wrap(json).asArray { + if let value = Element.init(value) { self.insert(value) } } } +} + +extension Set: JSONEncodable where Element: JSONEncodable { public var json: JSON { - return JSON(try JSON.array(self.map { element in - if let element = element as? JSONEncodable { + do { + return JSON.array(try map { element in let json = element.json - if let error = json.error { throw error } - return json.object - } - return element as Any - })) + if case let .error(error) = json { throw error } + return wrap(json).object + }) + } catch { + return .error(error) + } } } -extension Array: JSONCodable, DefaultInitable { +extension Array: JSONDecodable where Element: JSONDecodable { public init?(_ json: JSON) { - guard let dic = (try? [Element](decode: json)) else { return nil } - self = dic + guard let array = (try? [Element](decode: json)) else { return nil } + self = array } public init(decode json: JSON) throws { - guard let arr = json.array else { throw [Any].mismatchError(json: json) } - if let T = Element.self as? JSONDecodable.Type { - self = arr.flatMap { T.init(JSON(any: $0)) as! Element? } - } else { - throw JSON.Error.notConfirmTo(protocol: JSONDecodable.self, actual: Element.self) - } + guard let arr = wrap(json).array else { throw [Element].mismatchError(json: json) } + self = arr.compactMap { Element.init(JSON(any: $0)) } } +} + +extension Array: JSONEncodable where Element: JSONEncodable { public var json: JSON { - return JSON(try JSON.array(self.map { element in - if let element = element as? JSONEncodable { + do { + return JSON.array(try map { element in let json = element.json - if let error = json.error { throw error } - return json.object - } - return element as Any - })) + if case let .error(error) = json { throw error } + return wrap(json).object + }) + } catch { + return .error(error) + } } } -extension Array where Element == Any { +extension Dictionary: JSONDecodable where Key == String, Value: JSONDecodable { public init?(_ json: JSON) { - guard let dic = (try? [Element](decode: json)) else { return nil } + guard let dic = (try? [Key: Value](decode: json)) else { return nil } self = dic } public init(decode json: JSON) throws { - guard let arr = json.array else { throw [Any].mismatchError(json: json) } - self = arr + guard let dict = wrap(json).dict else { throw [String: Value].mismatchError(json: json) } + self = dict.flatMap { ($0, Value.init(JSON(any: $1))) } } } -extension Dictionary: JSONCodable, DefaultInitable { - - public init?(_ json: JSON) { - guard let dic = (try? [Key: Value](decode: json)) else { return nil } - self = dic - } - - public init(decode json: JSON) throws { - guard let dict = json.dict else { throw [String: Any].mismatchError(json: json) } - guard Key.self is String.Type else { - throw JSON.Error.notConfirmTo(protocol: String.self, actual: Key.self) - } - if let T = Value.self as? JSONDecodable.Type { - self = dict.flatMap { ($0.0 as! Key, T.init(JSON(any: $0.1)) as! Value?) } - } else { - throw JSON.Error.notConfirmTo(protocol: JSONDecodable.self, actual: Value.self) - } - } +extension Dictionary: JSONEncodable where Key == String, Value: JSONEncodable { public var json: JSON { - guard Key.self is String.Type else { - return .error(JSON.Error.notConfirmTo(protocol: String.self, actual: Key.self)) - } - return JSON(try JSON.object(self.map { (key, value) in - if let value = value as? JSONEncodable { + do { + return JSON.object(try map { (key, value) in let json = value.json - if let error = json.error { throw error } - return (key as! String, json.object) - } - return (key as! String, value as Any) - })) - } -} - -extension Dictionary where Key == String, Value == Any { - - public init?(_ json: JSON) { - guard let dict = json.dict else { return nil } - self = dict - } - - public init(decode json: JSON) throws { - guard let dict = json.dict else { throw [String: Any].mismatchError(json: json) } - self = dict + if case let .error(error) = json { throw error } + return (key, wrap(json).object) + }) + } catch { + return .error(error) + } } } diff --git a/FxJSON/JSON.swift b/FxJSON/JSON.swift index b2727f2..e90b52b 100755 --- a/FxJSON/JSON.swift +++ b/FxJSON/JSON.swift @@ -30,30 +30,36 @@ import Foundation //MARK: - JSON +@dynamicMemberLookup public enum JSON { + + public enum Number { + case double(Double) + case int(Int) + var object: Any { + switch self { + case .double(let any as Any), + .int(let any as Any): + return any + } + } + } + + public struct Null { } + case object([String: Any]) case array([Any]) case string(String) - case number(NSNumber) - case bool(Swift.Bool) + case number(Number) + case bool(Bool) case error(Swift.Error) case null } -public extension JSON { +//MARK: - Init - var object: Any { - switch self { - case let .object(any): return any - case let .array(any): return any - case let .string(any): return any - case let .number(any): return any - case let .bool(any): return any - case let .error(any): return any - default: return NSNull() - } - } +public extension JSON { init() { self = .null @@ -70,8 +76,10 @@ public extension JSON { case let num as NSNumber: if CFGetTypeID(num) == CFBooleanGetTypeID() { self = .bool(num.boolValue) + } else if let intValue = num as? Int { + self = .number(.int(intValue)) } else { - self = .number(num) + self = .number(.double(num.doubleValue)) } case let err as Swift.Error: self = .error(err) @@ -79,32 +87,6 @@ public extension JSON { self = .null } } - - var type: String { - switch self { - case .object: return "object" - case .array: return "array" - case .string: return "string" - case .number: return "number" - case .bool: return "boolen" - case .error: return "error" - case .null: return "null" - } - } - - var isNull: Bool { - return self == .null - } - - var isError: Bool { - if case .error = self { return true } - return false - } - - var error: Swift.Error? { - if case let .error(error) = self { return error } - return nil - } } //MARK: - Error handling @@ -114,11 +96,11 @@ public extension JSON { enum Error: Swift.Error, CustomStringConvertible { case initalize(error: Swift.Error) - case typeMismatch(expected: Any.Type, actual: String) + case typeMismatch(expected: Any.Type, actual: Any.Type) case notConfirmTo(protocol: Any.Type, actual: Any.Type) case encodeToJSON(wrongObject: Any) case notExist(dict: [String: Any], key: String) - case wrongType(subscript: JSON, key: Index) + case wrongType(subscript: JSON, key: JSONKeyConvertible) case outOfBounds(arr: [Any], index: Int) case formatter(format: String, value: String) case customTransfrom(source: Any) @@ -137,7 +119,7 @@ public extension JSON { case .notExist(dict: let dict, key: let key): return "Key: \"\(key)\" not exist, dict is: \(dict)" case .wrongType(subscript: let json, key: let key): - return "Cannot subscrpit key: \(key) to \(json.debugDescription)" + return "Cannot subscrpit key: \(key) to \(wrap(json).debugDescription)" case .outOfBounds(arr: let arr, index: let index): return "Subscript \(index) to \(arr) is out of bounds" case .formatter(format: let format, value: let value): @@ -157,7 +139,7 @@ extension JSON: ExpressibleByDictionaryLiteral { public init(dictionaryLiteral elements: (String, JSONEncodable)...) { var dict = [String: Any](minimumCapacity: elements.count) - for element in elements { dict[element.0] = element.1.json.object } + for element in elements { dict[element.0] = wrap(element.1.json).object } self = .object(dict) } } @@ -165,7 +147,7 @@ extension JSON: ExpressibleByDictionaryLiteral { extension JSON: ExpressibleByArrayLiteral { public init(arrayLiteral elements: JSONEncodable...) { - self = .array(elements.map { $0.json.object }) + self = .array(elements.map { wrap($0.json).object }) } } @@ -187,14 +169,14 @@ extension JSON: ExpressibleByStringLiteral { extension JSON: ExpressibleByIntegerLiteral { public init(integerLiteral value: IntegerLiteralType) { - self = .number(NSNumber(value: value)) + self = .number(.int(value)) } } extension JSON: ExpressibleByFloatLiteral { public init(floatLiteral value: FloatLiteralType) { - self = .number(NSNumber(value: value)) + self = .number(.double(value)) } } @@ -212,7 +194,7 @@ extension JSON: ExpressibleByNilLiteral { } } -//MARK: - convert to and from jsonData and jsonString +//MARK: - convert from jsonData and jsonString public extension JSON { @@ -222,92 +204,39 @@ public extension JSON { let object = try JSONSerialization.jsonObject(with: data, options: options) self.init(any: object) } catch { - self.init(JSON.error(JSON.Error.initalize(error: error))) + self = .error(JSON.Error.initalize(error: error)) } } init(jsonString: String?, options: JSONSerialization.ReadingOptions = []) { self.init(jsonData: jsonString?.data(using: String.Encoding.utf8), options: options) } - - func jsonData(withOptions opt: JSONSerialization.WritingOptions = []) throws -> Data { - guard JSONSerialization.isValidJSONObject(object) else { - throw error ?? Error.encodeToJSON(wrongObject: object) - } - return try JSONSerialization.data(withJSONObject: object, options: opt) - } - - func jsonString(withOptions opt: JSONSerialization.WritingOptions = [], - encoding ecd: String.Encoding = String.Encoding.utf8) throws -> String { - switch self { - case .object, .array: - let data = try self.jsonData(withOptions: opt) - if let jsonSrt = String(data: data, encoding: ecd) { return jsonSrt } - throw Error.encodeToJSON(wrongObject: ecd) - default: - throw error ?? Error.encodeToJSON(wrongObject: object) - } - } -} - -//MARK: - StringConvertible - -extension JSON: CustomStringConvertible, CustomDebugStringConvertible { - - public var description: String { - return (try? jsonString(withOptions: .prettyPrinted)) ?? "\(object)" - } - - public var debugDescription: String { - return "\(type): " + ((try? jsonString()) ?? "\(object)") - } } //MARK: - Equatable -extension JSON: Equatable { } - -public func ==(lhs: JSON, rhs: JSON) -> Bool { - switch (lhs, rhs) { - case let (.object(l as NSDictionary), .object(r as NSDictionary)): - return l == r - case let (.array(l as NSArray), .array(r as NSArray)): - return l == r - case let (.string(l), .string(r)): - return l == r - case let (.bool(l), .bool(r)): - return l == r - case let (.number(l), .number(r)): - return l == r - case (.null, .null): - return true - default: - return false +extension JSON: Equatable { + public static func ==(lhs: JSON, rhs: JSON) -> Bool { + switch (lhs, rhs) { + case let (.object(l as NSDictionary), .object(r as NSDictionary)): + return l == r + case let (.array(l as NSArray), .array(r as NSArray)): + return l == r + case let (.string(l), .string(r)): + return l == r + case let (.bool(l), .bool(r)): + return l == r + case let (.number(l), .number(r)): + return l == r + case (.null, .null): + return true + default: + return false + } } } -//MARK: - For - in - -public extension JSON { - - var dict: [String: Any]? { - guard case let .object(dic) = self else { return nil } - return dic - } - - var array: [Any]? { - guard case let .array(arr) = self else { return nil } - return arr - } - - var asDict: LazyMapCollection<[String: Any], (key: String, value: JSON)> { - return (dict ?? [:]).lazy.map { ($0.0, JSON(any: $0.1)) } - } - - var asArray: LazyMapCollection<[Any], JSON> { - return (array ?? []).lazy.map { JSON(any: $0) } - } -} +extension JSON.Number: Equatable { } //MARK: - Dictionary extension diff --git a/FxJSON/JSONWrapper.swift b/FxJSON/JSONWrapper.swift new file mode 100644 index 0000000..51bad01 --- /dev/null +++ b/FxJSON/JSONWrapper.swift @@ -0,0 +1,217 @@ +// +// JSON.swift +// FxJSON +// +// Created by Frain on 17/6/18. +// +// The MIT License (MIT) +// +// Copyright (c) 2016~2017 Frain +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +import Foundation + +public func wrap(_ json: JSON) -> JSON.Wrapper { + return JSON.Wrapper(json: json) +} + +public extension JSON { + public class Wrapper { + var json: JSON + + var getJSON: [(JSON) -> JSON] = [] + var setJSON: [(JSON) -> (inout JSON) -> ()] = [] + + init(json: JSON) { + self.json = json + } + } +} + +public extension JSON.Wrapper { + + var object: Any { + switch json { + case .object(let any as Any), + .array(let any as Any), + .string(let any as Any), + .bool(let any as Any), + .error(let any as Any): + return any + case .number(let number): + return number.object + case .null: + return JSON.Null() + } + } + + var type: Any.Type { + switch json { + case .object: return [String: Any].self + case .array: return [Any].self + case .string: return String.self + case .number: return JSON.Number.self + case .bool: return Bool.self + case .error: return Error.self + case .null: return JSON.Null.self + } + } + + var isNull: Bool { + if case .null = json { return true } + return false + } + + var isError: Bool { + if case .error = json { return true } + return false + } + + var error: Swift.Error? { + if case let .error(error) = json { return error } + return nil + } +} + +public extension JSON.Wrapper { + + func jsonData(withOptions opt: JSONSerialization.WritingOptions = []) throws -> Data { + guard JSONSerialization.isValidJSONObject(object) else { + throw error ?? JSON.Error.encodeToJSON(wrongObject: object) + } + return try JSONSerialization.data(withJSONObject: object, options: opt) + } + + func jsonString(withOptions opt: JSONSerialization.WritingOptions = [], + encoding ecd: String.Encoding = String.Encoding.utf8) throws -> String { + switch json { + case .object, .array: + let data = try self.jsonData(withOptions: opt) + if let jsonSrt = String(data: data, encoding: ecd) { return jsonSrt } + throw JSON.Error.encodeToJSON(wrongObject: ecd) + default: + throw error ?? JSON.Error.encodeToJSON(wrongObject: object) + } + } +} + +//MARK: - StringConvertible + +extension JSON.Wrapper: CustomStringConvertible, CustomDebugStringConvertible, CustomPlaygroundDisplayConvertible { + + public var description: String { + return (try? jsonString(withOptions: .prettyPrinted)) ?? "\(object)" + } + + public var debugDescription: String { + return "\(type): " + ((try? jsonString()) ?? "\(object)") + } + + public var playgroundDescription: Any { + return object + } +} + +extension JSON.Number: CustomStringConvertible, CustomDebugStringConvertible, CustomPlaygroundDisplayConvertible { + public var description: String { + return String(describing: object) + } + + public var debugDescription: String { + return String(describing: object) + } + + public var playgroundDescription: Any { + return object + } +} + +//MARK: - For - in + +public extension JSON.Wrapper { + + var dict: [String: Any]? { + guard case let .object(dic) = json else { return nil } + return dic + } + + var array: [Any]? { + guard case let .array(arr) = json else { return nil } + return arr + } + + var asDict: LazyMapCollection<[String: Any], (key: String, value: JSON)> { + return (dict ?? [:]).lazy.map { ($0.0, JSON(any: $0.1)) } + } + + var asArray: LazyMapCollection<[Any], JSON> { + return (array ?? []).lazy.map { JSON(any: $0) } + } +} + +//MARK: - Subscript + +private extension JSON.Wrapper { + + func setJSON(with value: JSON) -> (inout JSON) -> () { + guard !setJSON.isEmpty else { return { $0 = value } } + let (get, set) = (getJSON.remove(at: 0), setJSON.remove(at: 0)) + return { json in + var subJSON = get(json) + self.setJSON(with: value)(&subJSON) + if case .error = subJSON { json = subJSON; return } + set(subJSON)(&json) + } + } +} + +extension JSON.Wrapper { + + func set(json: JSON) { + setJSON(with: json)(&self.json) + } +} + +public extension JSON.Wrapper { + + subscript(ignoreIfNull path: JSONKeyConvertible...) -> JSON.Wrapper { + getJSON.append { $0[ignoreIfNull: path] } + setJSON.append { value in { (json: inout JSON) in json[ignoreIfNull: path] = value } } + return self + } + + subscript(path: JSONKeyConvertible...) -> JSON.Wrapper { + getJSON.append { $0[create: path] } + setJSON.append { value in { (json: inout JSON) in json[create: path] = value } } + return self + } + + subscript(index: JSONKeyConvertible) -> JSON.Wrapper { + getJSON.append { $0[create: index] } + setJSON.append { value in { (json: inout JSON) in json[create: index] = value } } + return self + } + + subscript(transform: Transform) -> JSON.Wrapper { + getJSON.append { $0[transform] } + setJSON.append { value in { (json: inout JSON) in json[transform] = value } } + return self + } +} diff --git a/FxJSON/Protocols.swift b/FxJSON/Protocols.swift index 339a577..dd630af 100755 --- a/FxJSON/Protocols.swift +++ b/FxJSON/Protocols.swift @@ -34,7 +34,7 @@ public protocol JSONEncodable { var json: JSON { get } - func encode(mapper: JSON.Mapper) + func encode(wrapper: JSON.Wrapper) static func specificOptions() -> [String: SpecificOption] } @@ -45,18 +45,18 @@ public extension JSONEncodable { return JSON(operate: encode) } - func encode(mapper: JSON.Mapper) { + func encode(wrapper: JSON.Wrapper) { let type = self is DefaultInitable ? Mirror(reflecting: self).subjectType : Self.self let info = Metadata(type: type) var mutableSelf = self let selfPointer = info.getPointer(of: &mutableSelf) for (name, type, offset) in info.properties { - var index = JSON.Index(stringLiteral: name) + let index = name var setJSON = { (json: inout JSON, value: JSON) in json[create: index] = value } if let options = Self.specificOptions()[name] { if options.contains(.ignore) { continue } if options.contains(.ignoreIfNull) { setJSON = { $0[ignoreIfNull: [index]] = $1 } } - if let idx = options.index { index = idx } +// if let idx = options.index { index = idx } let set = setJSON if let transform = options.transform { setJSON = { json, value in @@ -67,11 +67,11 @@ public extension JSONEncodable { } } guard let serializable = type as? JSONEncodable.Type else { - mapper.json = .error(JSON.Error.notConfirmTo(protocol: JSONEncodable.self, actual: type)) + wrapper.json = .error(JSON.Error.notConfirmTo(protocol: JSONEncodable.self, actual: type)) return } let value = serializable.fetchValue(from: selfPointer.advanced(by: offset)) - setJSON(&mapper.json, value.json) + setJSON(&wrapper.json, value.json) } } @@ -80,19 +80,19 @@ public extension JSONEncodable { } func jsonData(withOptions opt: JSONSerialization.WritingOptions = []) throws -> Data { - return try json.jsonData(withOptions: opt) + return try wrap(json).jsonData(withOptions: opt) } func jsonString(withOptions opt: JSONSerialization.WritingOptions = [], encoding ecd: String.Encoding = String.Encoding.utf8) throws -> String { - return try json.jsonString(withOptions: opt, encoding: ecd) + return try wrap(json).jsonString(withOptions: opt, encoding: ecd) } } public extension JSONEncodable where Self: RawRepresentable, Self.RawValue: JSONEncodable { - func encode(mapper: JSON.Mapper) { - mapper.json = self.rawValue.json + func encode(wrapper: JSON.Wrapper) { + wrapper.json = self.rawValue.json } } @@ -101,10 +101,6 @@ extension JSONEncodable { static func fetchValue(from pointer: UnsafeRawPointer) -> JSONEncodable { return pointer.load(as: Self.self) } - - static func mismatchError(json: JSON) -> Error { - return json.error ?? JSON.Error.typeMismatch(expected: Self.self, actual: json.type) - } } //MARK: - JSONDecodable @@ -127,7 +123,7 @@ public extension JSONDecodable { init(decode json: JSON) throws { let object = UnsafeMutablePointer.allocate(capacity: 1) - defer { object.deallocate(capacity: 1) } + defer { object.deallocate() } let rawObject = UnsafeMutableRawPointer(object) let info = Metadata(type: Self.self) let options = Self.specificOptions() @@ -160,6 +156,13 @@ public extension JSONDecodable { } } +extension JSONDecodable { + + static func mismatchError(json: JSON) -> Error { + return wrap(json).error ?? JSON.Error.typeMismatch(expected: Self.self, actual: wrap(json).type) + } +} + public extension JSONDecodable where Self: DefaultInitable { init(decode json: JSON) throws { @@ -173,7 +176,7 @@ public extension JSONDecodable where Self: DefaultInitable { let selfPointer = info.getPointer(of: &self) try info.properties.forEach { let value = try Self.fetchValue(property: $0, json: json, from: options) - type(of: value).update(value, into: selfPointer.advanced(by: $0.offset)) + Swift.type(of: value).update(value, into: selfPointer.advanced(by: $0.offset)) } } } @@ -192,11 +195,11 @@ where Self: RawRepresentable, Self.RawValue: JSONDecodable { extension JSONDecodable { static func fetchValue(property: Metadata.Property, json: JSON, from options: [String: SpecificOption]) throws -> JSONDecodable { - var index = JSON.Index(stringLiteral: property.key) + let index = property.key var getJSON = { json[index] } if let options = options[property.key] { - if let idx = options.index { index = idx } - if let idx = options.alertIndex, json[index].isError { index = idx } +// if let idx = options.index { index = idx } +// if let idx = options.alertIndex, wrap(json[index]).isError { index = idx } if let transform = options.transform { getJSON = { json[index][transform] } } if let value = options.defaultValue { guard type(of: value) == property.type, let deserializable = property.type as? JSONDecodable.Type else { @@ -284,12 +287,12 @@ public struct SpecificOption: OptionSet { public let rawValue: Int - let index: JSON.Index? - let alertIndex: JSON.Index? + let index: JSON.KeyPath? + let alertIndex: JSON.KeyPath? let transform: Transform? let defaultValue: Any? - init(rawValue: Int, index: JSON.Index? = nil, alertIndex: JSON.Index? = nil, + init(rawValue: Int, index: JSON.KeyPath? = nil, alertIndex: JSON.KeyPath? = nil, transform: Transform? = nil, defaultValue: Any? = nil) { self.rawValue = rawValue self.index = index @@ -311,12 +314,10 @@ public struct SpecificOption: OptionSet { } public mutating func formUnion(_ other: SpecificOption) { - let combinedIdx = index.flatMap { idx in other.index.map { JSON.Index.path([idx, $0]) } } - let combinedAidx = alertIndex.flatMap { idx in other.alertIndex.map { JSON.Index.path([idx, $0]) } } self = SpecificOption( rawValue: rawValue | other.rawValue, - index: combinedIdx ?? index ?? other.index, - alertIndex: combinedAidx ?? alertIndex ?? other.alertIndex, + index: index ?? other.index, + alertIndex: alertIndex ?? other.alertIndex, transform: transform ?? other.transform, defaultValue: defaultValue ?? other.defaultValue ) @@ -330,103 +331,20 @@ public struct SpecificOption: OptionSet { } } -extension SpecificOption: ExpressibleByStringLiteral { - - public init(stringLiteral value: String) { - self = SpecificOption.index(.key(.key(value))) - } - - public init(unicodeScalarLiteral value: String) { - self = SpecificOption.index(.key(.key(value))) - } - - public init(extendedGraphemeClusterLiteral value: String) { - self = SpecificOption.index(.key(.key(value))) - } -} - - -//MARK: - JSONMapper - -public extension JSON { - - final class Mapper { - - var json: JSON - - var getJSON: [(JSON) -> JSON] = [] - var setJSON: [(JSON) -> (inout JSON) -> ()] = [] - - init(json: JSON) { - self.json = json - } - - func set(json: JSON) { - setJSON(with: json)(&self.json) - } - - private func setJSON(with value: JSON) -> (inout JSON) -> () { - guard !setJSON.isEmpty else { return { $0 = value } } - let (get, set) = (getJSON.remove(at: 0), setJSON.remove(at: 0)) - return { json in - var subJSON = get(json) - self.setJSON(with: value)(&subJSON) - if subJSON.isError { json = subJSON; return } - set(subJSON)(&json) - } - } - } -} - -public extension JSON.Mapper { - - subscript(ignoreIfNull path: JSON.Index...) -> JSON.Mapper { - get { - getJSON.append { $0[ignoreIfNull: path] } - setJSON.append { value in { (json: inout JSON) in json[ignoreIfNull: path] = value } } - return self - } - } - - subscript(path: JSON.Index...) -> JSON.Mapper { - get { - getJSON.append { $0[create: path] } - setJSON.append { value in { (json: inout JSON) in json[create: path] = value } } - return self - } - } - - subscript(index: JSON.Index) -> JSON.Mapper { - get { - getJSON.append { $0[create: index] } - setJSON.append { value in { (json: inout JSON) in json[create: index] = value } } - return self - } - } - - subscript(transform: Transform) -> JSON.Mapper { - get { - getJSON.append { $0[transform] } - setJSON.append { value in { (json: inout JSON) in json[transform] = value } } - return self - } - } -} - //MARK: - operator postfix operator < public postfix func <(json: JSON) throws -> T { - return try json.decode() + return try wrap(json).decode() } -public func <<(lhs: JSON.Mapper, rhs: T) { +public func <<(lhs: JSON.Wrapper, rhs: T) { lhs.set(json: rhs.json) } -public func <<(lhs: JSON, rhs: JSON.Index) -> T? { - return try? lhs[rhs].decode() +public func <<(lhs: JSON, rhs: JSONKeyConvertible) -> T? { + return try? wrap(lhs[rhs]).decode() } //MARK: - Metadata @@ -558,7 +476,7 @@ struct Metadata { func properties() -> [Property] { var fieldNamePointer = self.fieldNamePointer - return (0.. + + subscript(dynamicMember key: String) -> JSON { + get { + switch (self, Int(key)) { + case let (.array(arr), index?): + if arr.indices.contains(index) { return JSON(any: arr[index]) } + return .error(Error.outOfBounds(arr: arr, index: index)) + case let (.object(dict), _): + if let o = dict[key] { return JSON(any: o) } + return .error(Error.notExist(dict: dict, key: key)) + case (.error, _): + return self + default: + return .error(Error.wrongType(subscript: self, key: key)) + } + } + set { + switch (self, Int(key)) { + case (.null, let index?): + guard index == 0 else { return } + self = .array([wrap(newValue)]) + case (.null, _): + self = .object([key: wrap(newValue).object]) + case (var .array(arr), let index?): + guard arr.indices.contains(index) else { return } + arr[index] = wrap(newValue).object + self = .array(arr) + case var (.object(dict), _): + dict[key] = wrap(newValue).object + self = .object(dict) + default: + break + } + } + } + + subscript(path: JSONKeyConvertible...) -> JSON { + get { + return self[path] + } + set { + self[path] = newValue + } + } + + subscript(path: [JSONKeyConvertible]) -> JSON { + get { + return path.reduce(self) { $0[$1] } + } + set { + guard !path.isEmpty else { self = newValue; return } + var path = path + let first = path.remove(at: 0) + self[first][path] = newValue + } + } + + subscript(key: JSONKeyConvertible) -> JSON { + get { + return self[dynamicMember: key.key] + } + set { + self[dynamicMember: key.key] = newValue + } + } + + subscript(transform: Transform) -> JSON { + get { + guard !wrap(self).isError, let from = transform.fromJSONFunc else { return self } + return JSON.init(try from(transform.jsonObjectType.init(decode: self))) + } + set { + guard !wrap(newValue).isError, let to = transform.toJSONFunc else { self = newValue; return } + self = JSON.init(try to(transform.objectType.init(decode: newValue))) + } + } +} + +extension JSON { + + subscript(ignoreIfNull path: [JSONKeyConvertible]) -> JSON { + get { + return self[create: path] + } + set { + if wrap(newValue).isNull { return } + self[create: path] = newValue + } + } + + subscript(create path: [JSONKeyConvertible]) -> JSON { + get { + return path.reduce(self) { $0[create: $1] } + } + set { + guard !path.isEmpty else { self = newValue; return } + var path = path + let first = path.remove(at: 0) + self[create: first][create: path] = newValue + } + } + + subscript(create key: JSONKeyConvertible) -> JSON { + get { + switch (self, Int(key.key)) { + case let (.array(arr), index?): + if arr.indices.contains(index) { return JSON(any: arr[index]) } + return .error(Error.outOfBounds(arr: arr, index: index)) + case let (.object(dict), _): + if let o = dict[key.key] { return JSON(any: o) } + return .error(Error.notExist(dict: dict, key: key.key)) + case (.error, _): + return self + default: + return JSON() + } + } + set { + switch (self, Int(key.key)) { + case (var .array(arr), let index?): + guard arr.indices.contains(index) else { return } + arr[index] = wrap(newValue).object + self = .array(arr) + case var (.object(dict), _): + dict[key.key] = wrap(newValue).object + self = .object(dict) + default: + break + } + } + } +} diff --git a/FxJSONTests/InitTests.swift b/FxJSONTests/InitTests.swift index ec7314e..efc30c6 100755 --- a/FxJSONTests/InitTests.swift +++ b/FxJSONTests/InitTests.swift @@ -97,9 +97,9 @@ class InitTests: XCTestCase { func testNumber() { let json: JSON = 1234567890.123456 XCTAssertEqual(json, JSON(any: 1234567890.123456)) - XCTAssertEqual(json.object as? Int, 1234567890) XCTAssertEqual(json.object as? Double, 1234567890.123456) - XCTAssertEqual(json.object as? Float, 1234567890.123456) + XCTAssertNotEqual(json.object as? Int, 1234567890) + XCTAssertNotEqual(json.object as? Float, 1234567890.123456) } func testNil() { diff --git a/FxJSONTests/SubscriptTests.swift b/FxJSONTests/SubscriptTests.swift index 10980de..1937c9f 100755 --- a/FxJSONTests/SubscriptTests.swift +++ b/FxJSONTests/SubscriptTests.swift @@ -72,7 +72,7 @@ class SubscriptTests: XCTestCase { json["arr", 3] = 0 json["arr"][4][0][0] = 0 - XCTAssertEqual(json["arr", 1].object as? Float, 2.899) + XCTAssertEqual(json["arr", 1].object as? Double, 2.899) XCTAssertEqual(json["arr", 2].object as? Double, 3.567) XCTAssertEqual(json["arr", 3].object as? Int, 0) XCTAssertEqual(json["arr"][4][0][0], 0) diff --git a/Test.playground/Contents.swift b/Test.playground/Contents.swift new file mode 100644 index 0000000..7ae1454 --- /dev/null +++ b/Test.playground/Contents.swift @@ -0,0 +1,52 @@ +import Foundation +import FxJSON + +var str = "Hello, playground" + +var json = JSON() + +json.string = "string" +json.int = 123 +json.double = 123.123 +json.object = json + +wrap(json) + +let dict = ["aaa":123, "bbb":123] + +let keyPath = \JSON.string + +dict.json + +struct SOme: Codable { + let string: String + let aa: Int + let bb: Double +} + +@_silgen_name("swift_getFieldAt") +func _getFieldAt( + _ type: Any.Type, + _ index: Int, + _ callback: @convention(c) (UnsafePointer, UnsafeRawPointer, UnsafeRawPointer) -> Void, + _ ctx: UnsafeRawPointer +) + +class User { + var name: String = "" + var age: Int = 0 +} + +var test = "hello" + +_getFieldAt(User.self, 1, { name, type, ctx in + let name = String(cString: name) + let type = unsafeBitCast(type, to: Any.Type.self) + if let jsonDecodable = type as? JSONDecodable.Type { + print(true) + } + print("\(name): \(type)") + print(ctx.assumingMemoryBound(to: String.self).pointee) +}, &test) + + diff --git a/Test.playground/contents.xcplayground b/Test.playground/contents.xcplayground new file mode 100644 index 0000000..9f5f2f4 --- /dev/null +++ b/Test.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file