diff --git a/.travis.yml b/.travis.yml index 3c34c1a..e306f85 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,13 @@ language: objective-c -osx_image: xcode7.3 -before_install: - - brew update - - brew uninstall xctool && brew install --HEAD xctool +osx_image: xcode8 +env: + matrix: + - TEST_TYPE=iOS + - TEST_TYPE=macOS script: - - xctool test -project Banana.xcodeproj -scheme 'Banana-iOS' -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO - - xctool test -project Banana.xcodeproj -scheme 'Banana-Mac' -sdk macosx ONLY_ACTIVE_ARCH=NO + - set -o pipefail + - if [ "$TEST_TYPE" = "iOS" ]; then + xcodebuild test -verbose -workspace Banana.xcworkspace -scheme Banana-iOS -sdk iphonesimulator -destination "platform=iOS Simulator,OS=10.0,name=iPhone 7" ONLY_ACTIVE_ARCH=NO; + elif [ "$TEST_TYPE" = "macOS" ]; then + xcodebuild test -verbose -workspace Banana.xcworkspace -scheme Banana-Mac -sdk macosx; + fi diff --git a/Banana.xcodeproj/project.pbxproj b/Banana.xcodeproj/project.pbxproj index a9ea650..b0eaeab 100644 --- a/Banana.xcodeproj/project.pbxproj +++ b/Banana.xcodeproj/project.pbxproj @@ -10,8 +10,6 @@ 7C76D4331CBE594A00363423 /* Banana.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CC8CCA01CBC107200896C9C /* Banana.framework */; }; 7CA20E9C1CBCFB8F0098EC8E /* Banana.h in Headers */ = {isa = PBXBuildFile; fileRef = 7CA20E9B1CBCFB8F0098EC8E /* Banana.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7CA2DF3B1CBFB3FA00E91622 /* Banana.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA2DF3A1CBFB3FA00E91622 /* Banana.swift */; }; - 7CC16FCD1CE9D7FE00AFA40A /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC16FCC1CE9D7FE00AFA40A /* Utils.swift */; }; - 7CC16FCE1CE9D7FE00AFA40A /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC16FCC1CE9D7FE00AFA40A /* Utils.swift */; }; 7CC8CCB91CBC176600896C9C /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC8CCB61CBC176600896C9C /* Types.swift */; }; 7CC8CCBA1CBC176600896C9C /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC8CCB71CBC176600896C9C /* Operators.swift */; }; 7CC8CCBB1CBC176600896C9C /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CC8CCB81CBC176600896C9C /* Functions.swift */; }; @@ -57,7 +55,6 @@ 7C76D42E1CBE594A00363423 /* Banana-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Banana-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 7CA20E9B1CBCFB8F0098EC8E /* Banana.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Banana.h; path = Support/Banana.h; sourceTree = SOURCE_ROOT; }; 7CA2DF3A1CBFB3FA00E91622 /* Banana.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Banana.swift; path = Sources/Banana.swift; sourceTree = ""; }; - 7CC16FCC1CE9D7FE00AFA40A /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Utils.swift; path = Sources/Utils.swift; sourceTree = ""; }; 7CC8CCA01CBC107200896C9C /* Banana.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Banana.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7CC8CCA51CBC107200896C9C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; name = Info.plist; path = Support/Info.plist; sourceTree = ""; }; 7CC8CCB61CBC176600896C9C /* Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Types.swift; path = Sources/Types.swift; sourceTree = ""; }; @@ -161,7 +158,6 @@ 7CC8CCB71CBC176600896C9C /* Operators.swift */, 7CC8CCB81CBC176600896C9C /* Functions.swift */, 7CA2DF3A1CBFB3FA00E91622 /* Banana.swift */, - 7CC16FCC1CE9D7FE00AFA40A /* Utils.swift */, ); name = Sources; sourceTree = ""; @@ -273,10 +269,12 @@ 7C76D42D1CBE594A00363423 = { CreatedOnToolsVersion = 7.3; LastSwiftMigration = 0800; + ProvisioningStyle = Manual; }; 7CC8CC9F1CBC107200896C9C = { CreatedOnToolsVersion = 7.3; LastSwiftMigration = 0800; + ProvisioningStyle = Manual; }; 7CE5B6BF1CC69CDE009D355F = { CreatedOnToolsVersion = 7.3; @@ -358,7 +356,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7CC16FCD1CE9D7FE00AFA40A /* Utils.swift in Sources */, 7CA2DF3B1CBFB3FA00E91622 /* Banana.swift in Sources */, 7CC8CCB91CBC176600896C9C /* Types.swift in Sources */, 7CC8CCBA1CBC176600896C9C /* Operators.swift in Sources */, @@ -370,7 +367,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7CC16FCE1CE9D7FE00AFA40A /* Utils.swift in Sources */, 7CE5B6D71CC69DBD009D355F /* Types.swift in Sources */, 7CE5B6D81CC69DBD009D355F /* Operators.swift in Sources */, 7CE5B6D91CC69DBD009D355F /* Functions.swift in Sources */, @@ -408,22 +404,22 @@ 7C76D4361CBE594A00363423 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.bhargavg.BananaTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 2.3; }; name = Debug; }; 7C76D4371CBE594A00363423 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.bhargavg.BananaTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 2.3; }; name = Release; }; @@ -470,6 +466,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 2.3; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -511,6 +508,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_VERSION = 2.3; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -522,6 +520,7 @@ isa = XCBuildConfiguration; buildSettings = { DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -532,7 +531,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.bhargavg.Banana; PRODUCT_NAME = Banana; SKIP_INSTALL = YES; - SWIFT_VERSION = 2.3; }; name = Debug; }; @@ -540,6 +538,7 @@ isa = XCBuildConfiguration; buildSettings = { DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -550,7 +549,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.bhargavg.Banana; PRODUCT_NAME = Banana; SKIP_INSTALL = YES; - SWIFT_VERSION = 2.3; }; name = Release; }; diff --git a/Package.swift b/Package.swift deleted file mode 100644 index a73788f..0000000 --- a/Package.swift +++ /dev/null @@ -1,6 +0,0 @@ -import PackageDescription - -let package = Package( - name: "Banana", - dependencies: [] -) diff --git a/README.md b/README.md index c06e7bd..5f092ce 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ Banana is a library that allows conversion of parsed JSON into typed objects. -Why another JSON mapper right? +Why another JSON mapper right? The idea behind creating Banana is to show that JSON mapping is not as complicated. -Simplicity and no-black-magic are the key design principles. The name Banana is chosen to signify this. +Simplicity and no-black-magic are the key design principles. The name Banana is chosen to signify this. If you are interested in how this libary has evolved, please read [this blog post series](http://bhargavg.com/swift/2016/03/29/functional-json-parsing-in-swift.html) @@ -16,13 +16,16 @@ If you are interested in how this libary has evolved, please read [this blog pos - Handles Optionals - Supports Keypaths -## Installation +## Understanding Version Numbers +`Banana` supports both legacy Swift 2.3 and latest Swift 3.0. To achieve this, the following release strategy is followed: -Only `Carthage` is supported as of now. Adding `CocoaPods` support is in pipeline. +* Odd number versions for `Swift 2.3` releases +* Even number versions for `Swift 3` releases -### [Carthage] +## Installation +Only `Carthage` is supported as of now. -[Carthage]: https://github.com/Carthage/Carthage +### [Carthage](https://github.com/Carthage/Carthage) To add this library to your project, just add the following to your `Cartfile` @@ -80,7 +83,7 @@ print(jsonString) ## Todo: - [x] Carthage Support - [ ] CocoaPods Support -- [x] SwiftPM Support +- [x] ~~SwiftPM Support~~ (Only supported with Swift-3 branch, ie., `master` branch) - [x] OS X, iOS Targets - [ ] Watch, TvOS Targets diff --git a/Sources/Banana.swift b/Sources/Banana.swift index 3b2213e..d43eaef 100644 --- a/Sources/Banana.swift +++ b/Sources/Banana.swift @@ -15,16 +15,6 @@ public struct Banana { - returns: An object of type T. This T is determined by the receiver. It tries to cast the AnyObject from `JSONObjectWithData` to a type that receiver expects. - throws: Throws `BananaError` if file couldn't be loaded or the value cannot be casted */ -#if swift(>=3.0) - public static func load(file: String, fileExtension: String = "json", bundle: Bundle = Bundle.main, options: JSONSerialization.ReadingOptions = []) throws -> T { - guard let path = bundle.pathForResource(file, ofType: fileExtension), - let data = NSData(contentsOfFile: path) else { - throw BananaError.Custom("Couldn't load JSON file: \(file)") - } - - return try load(data: data, options: options) - } -#else public static func load(file file: String, fileExtension: String = "json", bundle: NSBundle = NSBundle.mainBundle(), options: NSJSONReadingOptions = []) throws -> T { guard let path = bundle.pathForResource(file, ofType: fileExtension), let data = NSData(contentsOfFile: path) else { @@ -33,7 +23,6 @@ public struct Banana { return try load(data: data, options: options) } -#endif /** Parse the given data into a JSON object. @@ -47,15 +36,9 @@ public struct Banana { - returns: An object of type T. This T is determined by the receiver. It tries to cast the AnyObject from `JSONObjectWithData` to a type that receiver expects. - throws: Throws `BananaError` if JSON couldn't be parsed, or, the value cannot be casted */ -#if swift(>=3.0) - public static func load(data: NSData, options: JSONSerialization.ReadingOptions = []) throws -> T { - return try JSONSerialization.jsonObject(with: data as Data, options: options) <~~ get - } -#else public static func load(data data: NSData, options: NSJSONReadingOptions = []) throws -> T { return try NSJSONSerialization.JSONObjectWithData(data, options: options) <~~ get } -#endif /** Encode the given `jsonObject` into NSData @@ -72,20 +55,11 @@ public struct Banana { - throws: `BananaError` if the value cannot be encoded */ -#if swift(>=3.0) - public static func dump(options: JSONSerialization.WritingOptions) -> (jsonObject: AnyObject) throws -> NSData { - return { jsonObject in - return try Banana.dump(JSONObject: jsonObject, options: options) - } - } -#else public static func dump(options options: NSJSONWritingOptions) -> (jsonObject: AnyObject) throws -> NSData { return { jsonObject in return try Banana.dump(JSONObject: jsonObject, options: options) } } -#endif - /** Encode the given `jsonObject` into NSData @@ -99,15 +73,6 @@ public struct Banana { - throws: `BananaError` if the value cannot be encoded */ -#if swift(>=3.0) - public static func dump(JSONObject jsonObject: AnyObject, options: JSONSerialization.WritingOptions) throws -> NSData { - guard let jsonData = try? JSONSerialization.data(withJSONObject: jsonObject, options: options) else { - throw BananaError.Custom("Couldn't convert JSON Object to NSData") - } - - return jsonData - } -#else public static func dump(JSONObject jsonObject: AnyObject, options: NSJSONWritingOptions) throws -> NSData { guard let jsonData = try? NSJSONSerialization.dataWithJSONObject(jsonObject, options: options) else { throw BananaError.Custom("Couldn't convert JSON Object to NSData") @@ -115,7 +80,6 @@ public struct Banana { return jsonData } -#endif /** @@ -133,17 +97,6 @@ public struct Banana { - throws: `BananaError` if `NSString` object cannot be made */ -#if swift(>=3.0) - public static func toString(encoding: String.Encoding) -> (data: NSData) throws -> NSString { - return { data in - guard let string = NSString(data: data as Data, encoding: encoding.rawValue) else { - throw BananaError.Custom("Couldn't convert to NSString") - } - - return string - } - } -#else public static func toString(encoding encoding: NSStringEncoding) -> (data: NSData) throws -> NSString { return { data in guard let string = NSString(data: data, encoding: encoding) else { @@ -153,5 +106,4 @@ public struct Banana { return string } } -#endif } diff --git a/Sources/Functions.swift b/Sources/Functions.swift index faad55a..544a50f 100644 --- a/Sources/Functions.swift +++ b/Sources/Functions.swift @@ -10,17 +10,6 @@ import Foundation - throws: `BananaError` if the key is not found or the value cannot be casted. */ -#if swift(>=3.0) -public func get(_ box: JSON, key: String) throws -> T { - typealias BananaErrorType = BananaError - - guard let value = box[key] else { - throw BananaErrorType.NilValue(key) - } - - return try get(value) -} -#else public func get(box: JSON, key: String) throws -> T { typealias BananaErrorType = BananaError @@ -30,7 +19,6 @@ public func get(box: JSON, key: String) throws -> T { return try get(value) } -#endif /** A function to get the value from given JSON ditionary and cast it to the type that @@ -42,15 +30,9 @@ public func get(box: JSON, key: String) throws -> T { - throws:`BananaError` if the key is not found or the value cannot be casted. */ -#if swift(>=3.0) -public func get(_ box: JSON, keyPath path: String) throws -> T { - return try get(box) <~~ keyPath(path) -} -#else public func get(box: JSON, keyPath path: String) throws -> T { return try get(box) <~~ keyPath(path) } -#endif /** A function to get the value from given JSON dictonary and cast it to the type that @@ -67,47 +49,6 @@ public func get(box: JSON, keyPath path: String) throws -> T { - throws: `BananaError` if non keys are provided, or, the JSON doesn't have any of the keys, or, values could'nt be casted */ - -#if swift(>=3.0) -public func get(_ box: JSON, keys: [String]) throws -> T { - typealias BananaErrorType = BananaError - - guard !keys.isEmpty else { - throw BananaErrorType.Custom("Should provide at least one key") - } - - var errors:[BananaErrorType] = [] - - for key in keys { - do { - let value: T = try get(box, key: key) - return value - } catch { - if let err = error as? BananaErrorType { - errors.append(err) - } else { - errors.append(.Custom("Unknown error occured while processing keys: \(keys)")) - } - } - } - - guard let lastError = errors.last else { - throw BananaErrorType.Custom("Unknown error occured while processing keys: \(keys)") - } - - for error in errors { - if case .NilValue = error { - continue; - } else { - throw lastError - } - } - - let joinedKeys = keys.map{ "\"" + $0 + "\"" }.joined(separator: ", ") - - throw BananaErrorType.Custom("No values found for keys: \(joinedKeys) \nin: \n\(box)") -} -#else public func get(box: JSON, keys: [String]) throws -> T { typealias BananaErrorType = BananaError @@ -146,7 +87,6 @@ public func get(box: JSON, keys: [String]) throws -> T { throw BananaErrorType.Custom("No values found for keys: \(joinedKeys) \nin: \n\(box)") } -#endif /** Cast the given input into required type. @@ -158,15 +98,6 @@ public func get(box: JSON, keys: [String]) throws -> T { - throws: Throws `BananaError` if the value cannot be casted. */ -#if swift(>=3.0) -public func get(_ item: AnyObject) throws -> T { - guard let typedItem = item as? T else { - throw BananaError.InvalidType(item, expected: T.self) - } - - return typedItem -} -#else public func get(item: AnyObject) throws -> T { guard let typedItem = item as? T else { throw BananaError.InvalidType(item, expected: T.self) @@ -174,7 +105,6 @@ public func get(item: AnyObject) throws -> T { return typedItem } -#endif /** Get the value of a keypath from given dictonary. @@ -194,18 +124,6 @@ public func get(item: AnyObject) throws -> T { - throws: `BananaError` if the dictionary doesn't have a key path or the value cannot be casted. */ -#if swift(>=3.0) -public func keyPath(_ path: String) -> (box: JSON) throws -> T { - typealias BananaErrorType = BananaError - - return { box in - guard let value = (box as NSDictionary).value(forKeyPath: path) else { - throw BananaErrorType.NilValue(path) - } - return try get(value) - } -} -#else public func keyPath(path: String) -> (box: JSON) throws -> T { typealias BananaErrorType = BananaError @@ -216,4 +134,3 @@ public func keyPath(path: String) -> (box: JSON) throws -> T { return try get(value) } } -#endif diff --git a/Sources/Types.swift b/Sources/Types.swift index 15166d3..74db188 100644 --- a/Sources/Types.swift +++ b/Sources/Types.swift @@ -2,7 +2,7 @@ public typealias JSON = [String: AnyObject] /// Enumeration to represent Banana Errors. -public enum BananaError: ErrorProtocol { +public enum BananaError: ErrorType { /// Case when the value is of different type than expected case InvalidType(T, expected: U) /// Case when a value is nil @@ -22,4 +22,4 @@ extension BananaError: CustomStringConvertible { return "\(thingy)" } } -} \ No newline at end of file +} diff --git a/Sources/Utils.swift b/Sources/Utils.swift deleted file mode 100644 index 120bf6b..0000000 --- a/Sources/Utils.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// Utils.swift -// Banana -// -// Created by Bhargav Gurlanka on 5/16/16. -// Copyright © 2016 Bhargav Gurlanka. All rights reserved. -// - -import Foundation - - -#if swift(>=3.0) - -#else - public typealias ErrorProtocol = ErrorType -#endif - diff --git a/Support/Info.plist b/Support/Info.plist index 11d7458..8c0e4c2 100644 --- a/Support/Info.plist +++ b/Support/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.1 + 0.5 CFBundleSignature ???? CFBundleVersion diff --git a/Tests/GetTests.swift b/Tests/GetTests.swift index a2390f9..a95e4aa 100644 --- a/Tests/GetTests.swift +++ b/Tests/GetTests.swift @@ -46,13 +46,7 @@ class GetTests: XCTestCase { func testKeyPath() { do { - -#if swift(>=3.0) - let rawJSON: [String: AnyObject] = try Banana.load(file: "personWithTODOItems", fileExtension: "json", bundle: Bundle(for: GetTests.self)) -#else let rawJSON: [String: AnyObject] = try Banana.load(file: "personWithTODOItems", fileExtension: "json", bundle: NSBundle(forClass: GetTests.self)) -#endif - let stringKey: String = try get(rawJSON, keyPath: "address.home.street") let boolKey: Bool = try get(rawJSON, keyPath: "address.office.is_active") diff --git a/Tests/NestedObjectTests.swift b/Tests/NestedObjectTests.swift index 2e62a16..d03e691 100644 --- a/Tests/NestedObjectTests.swift +++ b/Tests/NestedObjectTests.swift @@ -13,12 +13,7 @@ class NestedObjectTests: XCTestCase { func testParsing() { do { - -#if swift(>=3.0) - let person = try Banana.load(file: "personWithTODOItems", fileExtension: "json", bundle: Bundle(for: GetTests.self)) <~~ Person.fromJSON -#else let person = try Banana.load(file: "personWithTODOItems", fileExtension: "json", bundle: NSBundle(forClass: GetTests.self)) <~~ Person.fromJSON -#endif XCTAssert(person.name == "Bob") XCTAssert(person.age == 25) diff --git a/Tests/PerfTests.swift b/Tests/PerfTests.swift index 0ec00d7..11b5ffc 100644 --- a/Tests/PerfTests.swift +++ b/Tests/PerfTests.swift @@ -12,15 +12,9 @@ import Banana class PerfTests: XCTestCase { func testPerformanceExample() { - -#if swift(>=3.0) - let json: [String: AnyObject] = try! Banana.load(file: "personWithTODOItems", fileExtension: "json", bundle: Bundle(for: PerfTests.self)) - let measureBlock = self.measure -#else let json: [String: AnyObject] = try! Banana.load(file: "personWithTODOItems", fileExtension: "json", bundle: NSBundle(forClass: PerfTests.self)) let measureBlock = self.measureBlock -#endif - + measureBlock { for _ in 0...1_000 { let _ = try! json <~~ Person.parse diff --git a/Tests/PlainObjectTests.swift b/Tests/PlainObjectTests.swift index cf898ad..c277c5f 100644 --- a/Tests/PlainObjectTests.swift +++ b/Tests/PlainObjectTests.swift @@ -13,12 +13,7 @@ class ParsingTests: XCTestCase { func testParsing() { do { - -#if swift(>=3.0) - let person = try Banana.load(file: "person", fileExtension: "json", bundle: Bundle(for: GetTests.self)) <~~ Person.fromJSON -#else let person = try Banana.load(file: "person", fileExtension: "json", bundle: NSBundle(forClass: GetTests.self)) <~~ Person.fromJSON -#endif XCTAssert(person.name == "Bob") XCTAssert(person.age == 25)