Skip to content

Commit 1d133ee

Browse files
authored
Better README.md (#16)
1 parent 9f3d994 commit 1d133ee

File tree

2 files changed

+158
-26
lines changed

2 files changed

+158
-26
lines changed

PerfTests/Package.resolved

Lines changed: 0 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 158 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,175 @@
1-
# swift-pure-json
1+
# pure-swift-json
22

33
[![Swift 5.1](https://img.shields.io/badge/Swift-5.1-blue.svg)](https://swift.org/download/)
44
[![github-actions](https://github.com/fabianfett/pure-swift-json/workflows/CI/badge.svg)](https://github.com/fabianfett/pure-swift-json/actions)
55
[![codecov](https://codecov.io/gh/fabianfett/pure-swift-json/branch/master/graph/badge.svg)](https://codecov.io/gh/fabianfett/pure-swift-json)
6-
![macOS](https://img.shields.io/badge/os-macOS-green.svg?style=flat)
7-
![tuxOS](https://img.shields.io/badge/os-tuxOS-green.svg?style=flat)
86

9-
This package provides a json encoder and decoder in pure Swift (without the use of Foundation).
10-
The implementation shall be [RFC8259](https://tools.ietf.org/html/rfc8259) complient.
7+
This package provides a json encoder and decoder in pure Swift (without the use of Foundation or any other dependency).
8+
The implementation is [RFC8259](https://tools.ietf.org/html/rfc8259) complient. It offers a significant performance improvement over the Foundation implementation on Linux.
119

10+
If you like the idea of using pure Swift without any dependencies you might also like my reimplementation of Base64 in pure Swift: [`swift-base64-kit`](https://github.com/fabianfett/swift-base64-kit)
1211

13-
### Goals
12+
‼️ **NOTE:** This is in a very early stage of development. Please take into account that the API might change while in prerelease.
13+
14+
## Goals
1415

1516
- [x] Does not use Foundation at all
16-
- [x] Does not use `unsafe` swift syntax
17+
- [x] Does not use `unsafe` Swift syntax
1718
- [x] No external dependencies other than the Swift STL
1819
- [x] Faster than Foundation implementation
1920

20-
### Status
21+
#### Currently not supported
2122

22-
**Note**: Don't use this. This is under heavy development and will be changed a lot!
23+
- custom encoder and decoder for [ `Data` and `Date`](#what-about-date-and-data)
24+
- parsing/decoding of [UTF-16 and UTF-32 encoded json](#utf-16-and-utf-32)
25+
- transform CodingKeys to camelCase or snake_case (I want to look into this)
2326

24-
Currently the focus areas are:
27+
#### Alternatives
2528

26-
- This needs **a lot** of tests to ensure correct working and safety against attackers
27-
- Fix all `preconditionFailure("unimplemented")`
28-
- Find a way how to allow parsing of `Date` and `Data`
29-
- Support decoding of other unicode encodings (currently only utf-8 supported)
29+
- [IkigaJSON](https://github.com/autimatisering/IkigaJSON) Super fast encoding and decoding especially for server side Swift code. Depends on `SwiftNIO`.
30+
- [Foundation Coding](https://github.com/apple/swift-corelibs-foundation/blob/master/Sources/Foundation/JSONEncoder.swift)
3031

31-
### Alternatives
32+
## Usage
3233

33-
- [IkigaJSON](https://github.com/autimatisering/IkigaJSON)
34-
- [Foundation Coding](https://github.com/apple/swift-corelibs-foundation/blob/master/Sources/Foundation/JSONEncoder.swift)
34+
Add `pure-swift-json` as dependency to your `Package.swift`:
35+
36+
```swift
37+
dependencies: [
38+
.package(url: "https://github.com/fabianfett/pure-swift-json.git", .upToNextMajore(from: "0.1.0")),
39+
],
40+
```
41+
42+
Add `PureSwiftJSONCoding` to the target you want to use it in.
43+
44+
```swift
45+
targets: [
46+
.target(
47+
name: "MyFancyTarget",
48+
dependencies: ["PureSwiftJSONCoding"]),
49+
]
50+
```
51+
52+
Use it as you would use the Foundation encoder and decoder.
53+
54+
```swift
55+
import PureSwiftJSONCoding
56+
57+
let bytesArray = try JSONEncoder().encode(myEncodable)
58+
let myDecodable = try JSONDecoder().decode(MyDecodable.self, from: bytes)
59+
```
60+
61+
## Performance
62+
63+
All tests have been run on a 2019 MacBook Pro (16" – 2,4 GHz 8-Core Intel Core i9). You can run the tests yourself
64+
by cloning this repo and
65+
66+
```bash
67+
# change dir to perf tests
68+
$ cd PerfTests
69+
70+
# compile in release mode - IMPORTANT ‼️
71+
$ swift build -c release
72+
73+
# run tests
74+
$ .build/release/PureSwiftJSONCodingPerfTests
75+
```
76+
77+
#### Encoding
78+
79+
| | macOS Swift 5.1 | macOS Swift 5.2 | Linux Swift 5.1 | Linux Swift 5.2 |
80+
|:--|:--|:--|:--|:--|
81+
| Foundation | 2.61s | 2.62s | 13.03s | 12.52s |
82+
| PureSwiftJSON | 1.23s | 1.25s | 1.13s | 1.05s |
83+
| Speedup | ~2x | ~2x | **~10x** | **~10x** |
84+
85+
86+
#### Decoding
87+
88+
| | macOS Swift 5.1 | macOS Swift 5.2 | Linux Swift 5.1 | Linux Swift 5.2 |
89+
|:--|:--|:--|:--|:--|
90+
| Foundation | 2.72s | 3.04s | 10.27s | 10.65s |
91+
| PureSwiftJSON | 1.70s | 1.72s | 1.39s | 1.16s |
92+
| Speedup | ~1.5x | ~1.5x | **~7x** | **~8x** |
93+
94+
## Workarounds
95+
96+
### What about `Date` and `Data`?
97+
98+
Date and Data are special cases for encoding and decoding. They do have default implementations that are kind off special:
99+
100+
- Date will be encoded as a float
101+
102+
Example: `2020-03-17 16:36:58 +0000` will be encoded as `606155818.503831`
103+
104+
- Data will be encoded as a numeric array.
105+
106+
Example: `0, 1, 2, 3, 255` will be encoded as: `[0, 1, 2, 3, 255]`
107+
108+
Yes that is the default implementation. Only Apple knows why it is not [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) and [Base64](https://en.wikipedia.org/wiki/Base64). 🙃
109+
Because I don't want to link against Foundation it is not possible to implement default encoding and decoding strategies for `Date` and `Data` as the Foundation implementation does. That's why, if you want to use another encoding/decoding strategy than the default you need to overwrite `encode(to: Encoder)` and `init(from: Decoder)`.
110+
111+
This could look like this:
112+
113+
```swift
114+
struct MyEvent: Decodable {
115+
116+
let eventTime: Date
117+
118+
enum CodingKeys: String, CodingKey {
119+
case eventTime
120+
}
121+
122+
init(from decoder: Decoder) {
123+
let container = try decoder.container(keyedBy: CodingKeys.self)
124+
let dateString = try container.decode(String.self, forKey: .eventTime)
125+
guard let timestamp = MyEvent.dateFormatter.date(from: dateString) else {
126+
let dateFormat = String(describing: MyEvent.dateFormatter.dateFormat)
127+
throw DecodingError.dataCorruptedError(forKey: .eventTime, in: container, debugDescription:
128+
"Expected date to be in format `\(dateFormat)`, but `\(dateFormat) does not forfill format`")
129+
}
130+
self.eventTime = timestamp
131+
}
132+
133+
private static let dateFormatter: DateFormatter = MyEvent.createDateFormatter()
134+
private static func createDateFormatter() -> DateFormatter {
135+
let formatter = DateFormatter()
136+
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
137+
formatter.timeZone = TimeZone(secondsFromGMT: 0)
138+
formatter.locale = Locale(identifier: "en_US_POSIX")
139+
return formatter
140+
}
141+
}
142+
```
143+
144+
You can find more information about [encoding and decoding custom types in Apple's documentation](https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types).
145+
146+
### UTF-16 and UTF-32
147+
148+
If your input is [UTF-16](https://en.wikipedia.org/wiki/UTF-16) or [UTF-32](https://en.wikipedia.org/wiki/UTF-32) encoded, you can easily convert it to UTF-8:
149+
150+
```swift
151+
let utf16 = UInt16[]() // your utf-16 encoded data
152+
let utf8 = Array(String(decoding: utf16, as: Unicode.UTF16.self).utf8)
153+
```
154+
155+
```swift
156+
let utf32 = UInt32[]() // your utf-32 encoded data
157+
let utf8 = Array(String(decoding: utf32, as: Unicode.UTF32.self).utf8)
158+
```
159+
160+
## Contributing
161+
162+
Please feel welcome and encouraged to contribute to `pure-swift-json`. This is a very young endeavour and help is always welcome.
163+
164+
If you've found a bug, have a suggestion or need help getting started, please open an Issue or a PR. If you use this package, I'd be grateful for sharing your experience.
165+
166+
Focus areas for the time being:
167+
168+
- ensuring safe use of nested containers while encoding and decoding
169+
- supporting camelCase and snakeCase aka [`KeyEncodingStrategy`](https://developer.apple.com/documentation/foundation/jsonencoder/keyencodingstrategy)
170+
171+
## Credits
172+
173+
- [@weissi](https://github.com/weissi) thanks for answering all my questions and for opening tickets [SR-12125](https://bugs.swift.org/browse/SR-12125) and [SR-12126](https://bugs.swift.org/browse/SR-12126)
174+
- [@dinhhungle](https://github.com/dinhhungle) thanks for your quality assurance. It helped a lot!
175+
- [@Ro-M](https://github.com/Ro-M) thanks for checking my README.md

0 commit comments

Comments
 (0)