Skip to content

Commit

Permalink
Merge pull request #18 from postmates/swift4
Browse files Browse the repository at this point in the history
Swift 4 support
  • Loading branch information
lilyball authored Feb 19, 2018
2 parents a5c00bc + 1f67df7 commit 577b7e2
Show file tree
Hide file tree
Showing 27 changed files with 2,976 additions and 854 deletions.
2 changes: 1 addition & 1 deletion .swift-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.0
4.0
4 changes: 2 additions & 2 deletions PMJSON.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "PMJSON"
s.version = "2.0.3"
s.version = "3.0.0"
s.summary = "Pure Swift JSON encoding/decoding library"
s.description = "PMJSON provides a pure-Swift strongly-typed JSON encoder/decoder as well as a set of convenience methods for converting to/from Foundation objects and for decoding JSON structures."

Expand All @@ -13,7 +13,7 @@ Pod::Spec.new do |s|

s.source = { :git => "https://github.com/postmates/PMJSON.git", :tag => "v#{s.version}" }

s.source_files = "Sources/*.{swift,h,m}",
s.source_files = "Sources/**/*.{swift,h,m}",

s.ios.deployment_target = '8.0'
s.osx.deployment_target = '10.9'
Expand Down
52 changes: 45 additions & 7 deletions PMJSON.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
/* Begin PBXBuildFile section */
0A0F2BAA1BF1BBE50095D290 /* JSONError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0F2BA91BF1BBE50095D290 /* JSONError.swift */; };
0A0F2BAC1BF30D1B0095D290 /* JSONObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0F2BAB1BF30D1B0095D290 /* JSONObject.swift */; };
0A550CCE203A28A700941526 /* SwiftEncoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A550CCD203A28A700941526 /* SwiftEncoderTests.swift */; };
0AE408A4203413AE00A73EBC /* SwiftDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE408A3203413AE00A73EBC /* SwiftDecoder.swift */; };
0AE408A62036A36200A73EBC /* SwiftDecoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE408A52036A36200A73EBC /* SwiftDecoderTests.swift */; };
0AE408A82036BEEF00A73EBC /* Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE408A72036BEEF00A73EBC /* Codable.swift */; };
0AE408AA2036C6A000A73EBC /* SwiftCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE408A92036C6A000A73EBC /* SwiftCodableTests.swift */; };
0AE408AD2038024E00A73EBC /* JSONKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE408AC2038024E00A73EBC /* JSONKey.swift */; };
0AE408B62038063500A73EBC /* SwiftEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AE408B52038063500A73EBC /* SwiftEncoder.swift */; };
9E0317C51D664B9000096D9E /* sample.json in Resources */ = {isa = PBXBuildFile; fileRef = 9E0317C41D664B9000096D9E /* sample.json */; };
9E0AD0C31C69B3360038B4F5 /* DecimalNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0AD0C21C69B3360038B4F5 /* DecimalNumber.swift */; };
9E1B24441BC73D7600E5BC19 /* PMJSON.h in Headers */ = {isa = PBXBuildFile; fileRef = 9E1B24431BC73D7600E5BC19 /* PMJSON.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -40,8 +47,16 @@
/* Begin PBXFileReference section */
0A0F2BA91BF1BBE50095D290 /* JSONError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONError.swift; sourceTree = "<group>"; };
0A0F2BAB1BF30D1B0095D290 /* JSONObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONObject.swift; sourceTree = "<group>"; };
0A550CCD203A28A700941526 /* SwiftEncoderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftEncoderTests.swift; sourceTree = "<group>"; };
0A79B5071E8AFE6700A8C727 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
0A79B5091E8B0BF500A8C727 /* LinuxMain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinuxMain.swift; sourceTree = "<group>"; };
0AE408A3203413AE00A73EBC /* SwiftDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftDecoder.swift; sourceTree = "<group>"; };
0AE408A52036A36200A73EBC /* SwiftDecoderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftDecoderTests.swift; sourceTree = "<group>"; };
0AE408A72036BEEF00A73EBC /* Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Codable.swift; sourceTree = "<group>"; };
0AE408A92036C6A000A73EBC /* SwiftCodableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftCodableTests.swift; sourceTree = "<group>"; };
0AE408AC2038024E00A73EBC /* JSONKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONKey.swift; sourceTree = "<group>"; };
0AE408AE2038028100A73EBC /* PMJSON.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = PMJSON.podspec; sourceTree = "<group>"; };
0AE408B52038063500A73EBC /* SwiftEncoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftEncoder.swift; sourceTree = "<group>"; };
9E0317C41D664B9000096D9E /* sample.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = sample.json; sourceTree = "<group>"; };
9E0317C61D664D7D00096D9E /* README.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.txt; sourceTree = "<group>"; };
9E0AD0C21C69B3360038B4F5 /* DecimalNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DecimalNumber.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -95,10 +110,21 @@
path = Tests;
sourceTree = "<group>";
};
0AE408AB2038023D00A73EBC /* Coders */ = {
isa = PBXGroup;
children = (
0AE408A3203413AE00A73EBC /* SwiftDecoder.swift */,
0AE408B52038063500A73EBC /* SwiftEncoder.swift */,
0AE408AC2038024E00A73EBC /* JSONKey.swift */,
);
path = Coders;
sourceTree = "<group>";
};
9E1B24271BC73D1900E5BC19 = {
isa = PBXGroup;
children = (
0A79B5071E8AFE6700A8C727 /* Package.swift */,
0AE408AE2038028100A73EBC /* PMJSON.podspec */,
9E49DEF31C1F9E2F004FDCAD /* README.md */,
9E69BE0B1C6E843D00D92762 /* LICENSE-APACHE */,
9E49DEF21C1F9E2F004FDCAD /* LICENSE-MIT */,
Expand Down Expand Up @@ -126,6 +152,8 @@
0A0F2BA91BF1BBE50095D290 /* JSONError.swift */,
9E87937B1BC8341500BC6A15 /* Foundation.swift */,
9E0AD0C21C69B3360038B4F5 /* DecimalNumber.swift */,
0AE408A72036BEEF00A73EBC /* Codable.swift */,
0AE408AB2038023D00A73EBC /* Coders */,
9E1B246A1BC7463D00E5BC19 /* Parser.swift */,
9E8793771BC7547800BC6A15 /* Decoder.swift */,
9E5983DB1C60282F008495F6 /* Encoder.swift */,
Expand All @@ -144,6 +172,9 @@
9EC4CD951DB83DE8003A4268 /* JSONParserTests.swift */,
9E4997EF1DC96E110049EECD /* JSONEncoderTests.swift */,
9ECD04F61DC15CAD00DBE7CA /* JSONTestSuite.swift */,
0AE408A92036C6A000A73EBC /* SwiftCodableTests.swift */,
0AE408A52036A36200A73EBC /* SwiftDecoderTests.swift */,
0A550CCD203A28A700941526 /* SwiftEncoderTests.swift */,
9E0317C61D664D7D00096D9E /* README.txt */,
9E0317C41D664B9000096D9E /* sample.json */,
9ECD04F41DC15C9800DBE7CA /* JSONTestSuite */,
Expand Down Expand Up @@ -210,15 +241,15 @@
attributes = {
LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0900;
ORGANIZATIONNAME = Postmates;
ORGANIZATIONNAME = "Kevin Ballard";
TargetAttributes = {
9E1B24401BC73D7600E5BC19 = {
CreatedOnToolsVersion = 7.0.1;
LastSwiftMigration = 0800;
LastSwiftMigration = 0920;
};
9E1B24491BC73D7600E5BC19 = {
CreatedOnToolsVersion = 7.0.1;
LastSwiftMigration = 0800;
LastSwiftMigration = 0920;
};
};
};
Expand Down Expand Up @@ -270,9 +301,13 @@
9E8793781BC7547800BC6A15 /* Decoder.swift in Sources */,
0A0F2BAA1BF1BBE50095D290 /* JSONError.swift in Sources */,
9E0AD0C31C69B3360038B4F5 /* DecimalNumber.swift in Sources */,
0AE408A82036BEEF00A73EBC /* Codable.swift in Sources */,
9E1B24591BC73DA000E5BC19 /* JSON.swift in Sources */,
9E87937E1BC835F500BC6A15 /* Accessors.swift in Sources */,
0AE408A4203413AE00A73EBC /* SwiftDecoder.swift in Sources */,
0A0F2BAC1BF30D1B0095D290 /* JSONObject.swift in Sources */,
0AE408B62038063500A73EBC /* SwiftEncoder.swift in Sources */,
0AE408AD2038024E00A73EBC /* JSONKey.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -282,8 +317,11 @@
files = (
9EC4CD961DB83DE8003A4268 /* JSONParserTests.swift in Sources */,
9ECD04F71DC15CAD00DBE7CA /* JSONTestSuite.swift in Sources */,
0A550CCE203A28A700941526 /* SwiftEncoderTests.swift in Sources */,
0AE408AA2036C6A000A73EBC /* SwiftCodableTests.swift in Sources */,
9E4997F01DC96E110049EECD /* JSONEncoderTests.swift in Sources */,
9EC52A6A1DCBC2E900658723 /* JSONAccessorTests.swift in Sources */,
0AE408A62036A36200A73EBC /* SwiftDecoderTests.swift in Sources */,
9E1B24501BC73D7600E5BC19 /* JSONDecoderTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -421,7 +459,7 @@
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos watchsimulator watchos appletvsimulator appletvos";
SWIFT_INSTALL_OBJC_HEADER = NO;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2,3,4";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
Expand Down Expand Up @@ -475,7 +513,7 @@
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos watchsimulator watchos appletvsimulator appletvos";
SWIFT_INSTALL_OBJC_HEADER = NO;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 3.0;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2,3,4";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
Expand Down Expand Up @@ -529,7 +567,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
SWIFT_VERSION = 4.0;
};
name = Debug;
};
Expand Down Expand Up @@ -572,7 +610,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 3.0;
SWIFT_VERSION = 4.0;
VALIDATE_PRODUCT = YES;
};
name = Release;
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:3.0
// swift-tools-version:4.0

import PackageDescription

Expand Down
37 changes: 29 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# PMJSON

[![Version](https://img.shields.io/badge/version-v2.0.3-blue.svg)](https://github.com/postmates/PMJSON/releases/latest)
[![Version](https://img.shields.io/badge/version-v3.0.0-blue.svg)](https://github.com/postmates/PMJSON/releases/latest)
![Platforms](https://img.shields.io/badge/platforms-ios%20%7C%20osx%20%7C%20watchos%20%7C%20tvos-lightgrey.svg)
![Languages](https://img.shields.io/badge/languages-swift-orange.svg)
![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)
Expand Down Expand Up @@ -71,6 +71,8 @@ struct Config {
}
```

This library also provides support for `Swift.Encoder` and `Swift.Decoder`. See [this section](#swiftencoder-and-swiftdecoder) for details.

### Parsing

The JSON decoder is split into separate parser and decoder stages. The parser consums any sequence of unicode scalars, and produces a sequence of JSON "events" (similar to a SAX XML parser). The decoder accepts a sequence of JSON events and produces a `JSON` value. This architecture is designed such that you can use just the parser alone in order to decode directly to your own data structures and bypass the `JSON` representation entirely if desired. However, most clients are expected to use both components, and this is exposed via a simple method `JSON.decode(_:options:)`.
Expand Down Expand Up @@ -177,6 +179,18 @@ The `JSON` type has static methods `map()` and `flatMap()` for working with arra

There are also helpers for converting to/from Foundation objects. `JSON` offers an initializer `init(ns: AnyObject) throws` that converts from any JSON-compatible object to a `JSON`. `JSON` and `JSONObject` both offer the property `.ns`, which returns a Foundation object equivalent to the `JSON`, and `.nsNoNull` which does the same but omits any `null` values instead of using `NSNull`.

### Codable support

The `JSON` type conforms to `Codable`, so it can be encoded to a `Swift.Encoder` and decoded from a `Swift.Decoder`. This has been tested against the standard library-provided `JSONEncoder` and `JSONDecoder`. Due to limitations in the decoding protocol, decoding a `JSON` must attempt to decode multiple different types of values, so it's possible that a poorly-written `Swift.Decoder` may produce surprising results when decoding a `JSON`.

Encoding to a `JSON.Encoder` and decoding from a `JSON.Decoder` is optimized to avoid unnecessary work.

### `Swift.Encoder` and `Swift.Decoder`

This library provides an implementation of `Swift.Encoder` called `JSON.Encoder`. This can encode any `Encodable` to a `JSON`, a `String`, or a `Data`. It's used similarly to `Swift.JSONEncoder` (except at this time it doesn't have options to control encoding of specific types).

This library provides an implementation of `Swift.Decoder` called `JSON.Decoder`. This can decode any `Decodable` from a `JSON`, a `String`, or a `Data`. It's used similar to `Swift.JSONDecoder` (except at this time it doesn't have options to control decoding of specific types).

### Performance

The test suite includes some basic performance tests. Decoding ~70KiB of JSON using PMJSON takes about 2.5-3x the time that `NSJSONSerialization` does, though I haven't tested this with different distributions of inputs and it's possible this performance is specific to the characteristics of the test input. However, encoding the same JSON back to a `Data` is actually faster with PMJSON, taking around 75% of the time that `NSJSONSerialization` does.
Expand All @@ -197,7 +211,7 @@ The [Swift Package Manager][] may be used to install PMJSON by adding it to your
let package = Package(
name: "YourPackage",
dependencies: [
.Package(url: "https://github.com/postmates/PMJSON.git", majorVersion: 2)
.Package(url: "https://github.com/postmates/PMJSON.git", majorVersion: 3)
]
)
```
Expand All @@ -209,27 +223,27 @@ let package = Package(
To install using [Carthage][], add the following to your Cartfile:

```
github "postmates/PMJSON" ~> 2.0
github "postmates/PMJSON" ~> 3.0
```

This release supports Swift 3. If you want Swift 2.3 support, you can use
This release supports Swift 4. If you want Swift 3.x support, you can use

```
github "postmates/PMJSON" ~> 0.9.4
github "postmates/PMJSON" ~> 2.0
```

### CocoaPods

To install using [CocoaPods][], add the following to your Podfile:

```
pod 'PMJSON', '~> 2.0'
pod 'PMJSON', '~> 3.0'
```

This release supports Swift 3. If you want Swift 2.3 support, you can use
This release supports Swift 4. If you want Swift 3.x support, you can use

```
pod 'PMJSON', '~> 0.9.4'
pod 'PMJSON', '~> 2.0'
```

[CocoaPods]: https://cocoapods.org
Expand All @@ -248,6 +262,13 @@ Unless you explicitly state otherwise, any contribution intentionally submitted

## Version History

#### v3.0.0 (2018-02-18)

* Convert to Swift 4.
* Implement `Codable` on `JSON`.
* Add a `Swift.Decoder` implementation called `JSON.Decoder`.
* Add a `Swift.Encoder` implementation called `JSON.Encoder`.

#### v2.0.3 (2017-09-12)

* Add Linux support for `Decimal` (on Swift 3.1 and later). NOTE: Decimal support is still buggy in Swift 3.1, and the workarounds we employ to get the correct values on Apple platforms don't work on Linux. You probably shouldn't rely on this working correctly on Linux until Swift fixes its Decimal implementation.
Expand Down
46 changes: 18 additions & 28 deletions Sources/Accessors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@
// except according to those terms.
//

#if os(iOS) || os(OSX) || os(watchOS) || os(tvOS) || swift(>=3.1)
import class Foundation.NSDecimalNumber
import struct Foundation.Decimal
#endif
import class Foundation.NSDecimalNumber
import struct Foundation.Decimal

public extension JSON {
/// Returns `true` iff the receiver is `.null`.
Expand Down Expand Up @@ -71,13 +69,7 @@ public extension JSON {
/// On platforms where `.decimal` is a dummy value, it's not a treated as a number.
var isNumber: Bool {
switch self {
case .int64, .double: return true
case .decimal:
#if os(iOS) || os(OSX) || os(watchOS) || os(tvOS) || swift(>=3.1)
return true
#else
return false
#endif
case .int64, .double, .decimal: return true
default: return false
}
}
Expand Down Expand Up @@ -169,7 +161,7 @@ public extension JSON {
var int: Int? {
get {
guard let value = self.int64 else { return nil}
let truncated = Int(truncatingBitPattern: value)
let truncated = Int(truncatingIfNeeded: value)
guard Int64(truncated) == value else { return nil }
return truncated
}
Expand All @@ -192,12 +184,8 @@ public extension JSON {
case .int64(let i): return Double(i)
case .double(let d): return d
case .decimal(let d):
#if os(iOS) || os(OSX) || os(watchOS) || os(tvOS) || swift(>=3.1)
// NB: Decimal does not have any accessor to produce a Double
return NSDecimalNumber(decimal: d).doubleValue
#else
return nil
#endif
// NB: Decimal does not have any accessor to produce a Double
return NSDecimalNumber(decimal: d).doubleValue
default: return nil
}
}
Expand Down Expand Up @@ -304,16 +292,18 @@ internal func convertDoubleToInt64(_ d: Double) -> Int64? {
return Int64(d)
}

#if os(iOS) || os(OSX) || os(watchOS) || os(tvOS) || swift(>=3.1)
internal func convertDecimalToInt64(_ d: Decimal) -> Int64? {
if d > Int64.maxDecimal || d < Int64.minDecimal {
return nil
}
// NB: Decimal does not have any appropriate accessor
return NSDecimalNumber(decimal: d).int64Value
internal func convertDecimalToInt64(_ d: Decimal) -> Int64? {
if d > Int64.maxDecimal || d < Int64.minDecimal {
return nil
}
#else
internal func convertDecimalToInt64(_ d: DecimalPlaceholder) -> Int64? {
// NB: Decimal does not have any appropriate accessor
return NSDecimalNumber(decimal: d).int64Value
}

internal func convertDecimalToUInt64(_ d: Decimal) -> UInt64? {
if d > UInt64.maxDecimal || d < UInt64.minDecimal {
return nil
}
#endif
// NB: Decimal does not have any appropriate accessor
return NSDecimalNumber(decimal: d).uint64Value
}
Loading

0 comments on commit 577b7e2

Please sign in to comment.