Skip to content

rename the CodingKeys requirement to CodingKey, it reads more fluently #72

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Benchmarks/Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@
"repositoryURL": "https://github.com/apple/swift-atomics.git",
"state": {
"branch": null,
"revision": "ff3d2212b6b093db7f177d0855adbc4ef9c5f036",
"version": "1.0.3"
"revision": "6c89474e62719ddcc1e9614989fff2f68208fe10",
"version": "1.1.0"
}
},
{
"package": "swift-grammar",
"repositoryURL": "https://github.com/kelvin13/swift-grammar",
"repositoryURL": "https://github.com/tayloraswift/swift-grammar",
"state": {
"branch": null,
"revision": "69613825b2ad1d0538c59d72e548867ce7568cc2",
"version": "0.3.1"
"revision": "3ed94966b1fd8145c2624a5955a9f0d17663280b",
"version": "0.3.2"
}
},
{
Expand Down
38 changes: 20 additions & 18 deletions Benchmarks/Sources/AmazonIPAddressManager/Main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import Grammar
import JSONDecoding
import SystemExtras

struct Log:Decodable
struct Log:Decodable
{
let timestamp:String
let level:String
let thread:String
let logger:String
let message:String
enum CodingKeys:String, CodingKey

enum CodingKeys:String, Swift.CodingKey
{
case timestamp = "@timestamp"
case level
Expand All @@ -23,7 +23,9 @@ struct Log:Decodable
}
extension Log:JSONObjectDecodable
{
init(json:JSON.ObjectDecoder<CodingKeys>) throws
typealias CodingKey = CodingKeys

init(json:JSON.ObjectDecoder<CodingKey>) throws
{
self.timestamp = try json[.timestamp].decode()
self.level = try json[.level].decode()
Expand All @@ -33,28 +35,28 @@ extension Log:JSONObjectDecodable
}
}

@main
@main
struct Main:ParsableCommand
{
@Argument(help: "path to test data file (aws ip address manager log, jsonl format)")
var path:String
var path:String

func run() throws
{
let data:String = try FilePath.init(self.path).read()
let clock:SuspendingClock = .init()

let swiftJSONWithLinter:Duration = try clock.measure
let swiftJSONWithLinter:Duration = try clock.measure
{
let logs:[Log] = try Self.benchmarkSwiftJSONWithLinter(data)
print("swift-json: decoded \(logs.count) logs")
}
let swiftJSON:Duration = try clock.measure
let swiftJSON:Duration = try clock.measure
{
let logs:[Log] = try Self.benchmarkSwiftJSON(data)
print("swift-json: decoded \(logs.count) logs")
}
let foundation:Duration = try clock.measure
let foundation:Duration = try clock.measure
{
let logs:[Log] = try Self.benchmarkFoundation(data)
print("foundation: decoded \(logs.count) logs")
Expand All @@ -63,41 +65,41 @@ struct Main:ParsableCommand
print("swift-json decoding time (compatibility api): \(swiftJSON)")
print("foundation decoding time: \(foundation)")
}

@inline(never)
static
static
func benchmarkSwiftJSONWithLinter(_ string:String) throws -> [Log]
{
var logs:[Log] = []
var input:ParsingInput<NoDiagnostics<String.UTF8View>> = .init(string.utf8)
while let log:[(key:JSON.Key, value:JSON)] =
while let log:[(key:JSON.Key, value:JSON)] =
input.parse(as: JSON.Rule<String.Index>.Object?.self)
{
logs.append(try .init(json: .object(.init(log))))
}
return logs
}
@inline(never)
static
static
func benchmarkSwiftJSON(_ string:String) throws -> [Log]
{
var logs:[Log] = []
var input:ParsingInput<NoDiagnostics<String.UTF8View>> = .init(string.utf8)
while let log:[(key:JSON.Key, value:JSON)] =
while let log:[(key:JSON.Key, value:JSON)] =
input.parse(as: JSON.Rule<String.Index>.Object?.self)
{
logs.append(try .init(from: JSON.object(.init(log)) as any Decoder))
}
return logs
}
@inline(never)
static
static
func benchmarkFoundation(_ string:String) throws -> [Log]
{
let decoder:JSONDecoder = .init()
return try string.split(whereSeparator: \.isNewline).map
{
try decoder.decode(Log.self, from: $0.data(using: .utf8) ?? .init())
return try string.split(whereSeparator: \.isNewline).map
{
try decoder.decode(Log.self, from: $0.data(using: .utf8) ?? .init())
}
}
}
56 changes: 28 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div align="center">
***`json`***<br>`0.5.0`

***`json`***<br>`0.6.0`

[![ci build status](https://github.com/tayloraswift/swift-json/actions/workflows/build.yml/badge.svg)](https://github.com/tayloraswift/swift-json/actions/workflows/build.yml)
[![ci devices build status](https://github.com/tayloraswift/swift-json/actions/workflows/build-devices.yml/badge.svg)](https://github.com/tayloraswift/swift-json/actions/workflows/build-devices.yml)
[![ci benchmarks status](https://github.com/tayloraswift/swift-json/actions/workflows/benchmarks.yml/badge.svg)](https://github.com/tayloraswift/swift-json/actions/workflows/benchmarks.yml)
Expand All @@ -26,20 +26,20 @@ To parse a complete JSON message, use its [`init(parsing:)`](https://swiftinit.o
> [`DecodingWithCodable.swift`](Snippets/DecodingWithCodable.swift)

```swift
import JSON
import JSON

struct Decimal:Codable
struct Decimal:Codable
{
let units:Int
let places:Int
let units:Int
let places:Int
}
struct Response:Codable
struct Response:Codable
{
let success:Bool
let success:Bool
let value:Decimal
}

let string:String =
let string:String =
"""
{"success":true,"value":0.1}
"""
Expand All @@ -54,7 +54,7 @@ $ swift run DecodingWithCodable
Response(success: true, value: Decimal(units: 1, places: 1))
```

The rule is called “[`Root`](https://swiftinit.org/reference/swift-json/json/json/rule/root)” because it will match only complete JSON messages (objects or arrays).
The rule is called “[`Root`](https://swiftinit.org/reference/swift-json/json/json/rule/root)” because it will match only complete JSON messages (objects or arrays).
Like most `swift-grammar`-based [parsers](https://swiftinit.org/reference/swift-grammar/grammar/parsingrule), [`JSON.Rule`](https://swiftinit.org/reference/swift-json/json/json/rule) is generic over its input, which means you can parse directly from some [`Collection`](https://swiftinit.org/reference/swift/collection) of [`UInt8`](https://swiftinit.org/reference/swift/uint8).

`swift-json`’s constructive parsing engine also allows you to get diagnostics for invalid JSON messages:
Expand All @@ -65,17 +65,17 @@ Like most `swift-grammar`-based [parsers](https://swiftinit.org/reference/swift-
import Grammar
import JSON

let invalid:String =
let invalid:String =
"""
{"success":true,value:0.1}
"""
do
do
{
let _:JSON = try JSON.Rule<String.Index>.Root.parse(diagnosing: invalid.utf8)
}
catch let error as ParsingError<String.Index>
catch let error as ParsingError<String.Index>
{
let annotated:String = error.annotate(source: invalid,
let annotated:String = error.annotate(source: invalid,
renderer: String.init(_:),
newline: \.isNewline)
print(annotated)
Expand Down Expand Up @@ -115,26 +115,26 @@ You can be more selective about the form of the JSON you expect to receive by us
* [`Array`](https://swiftinit.org/reference/swift-json/json/json/rule/array)


The [`JSON`](https://swiftinit.org/reference/swift-json/json) module supports parsing arbitrary JSON fragments using the [`JSON.Rule<Location>.Value`](https://swiftinit.org/reference/swift-json/json/json/rule/value) rule.
The [`JSON`](https://swiftinit.org/reference/swift-json/json) module supports parsing arbitrary JSON fragments using the [`JSON.Rule<Location>.Value`](https://swiftinit.org/reference/swift-json/json/json/rule/value) rule.

The nature of constructive parsing also means it is straightforward to parse *multiple* concatenated JSON messages, as is commonly encountered when interfacing with streaming JSON APIs.

## adding `swift-json` as a dependency
## adding `swift-json` as a dependency

To use `swift-json` in a project, add the following to your `Package.swift` file:

```swift
let package = Package(
...
dependencies:
dependencies:
[
// other dependencies
.package(url: "https://github.com/tayloraswift/swift-json", from: "0.5.0"),
],
targets:
targets:
[
.target(name: "example",
dependencies:
.target(name: "example",
dependencies:
[
.product(name: "JSON", package: "swift-json"),
// other dependencies
Expand All @@ -144,13 +144,13 @@ let package = Package(
)
```

## building and previewing documentation
## building and previewing documentation

`swift-json` is DocC-compatible. However, its documentation is a lot more useful when built with a documentation engine like [`swift-biome`](https://github.com/tayloraswift/swift-biome).

### 1. gather the documentation files

`swift-json` uses the [`swift-package-catalog`](https://github.com/tayloraswift/swift-package-catalog) plugin to gather its documentation.
`swift-json` uses the [`swift-package-catalog`](https://github.com/tayloraswift/swift-package-catalog) plugin to gather its documentation.

Run the `catalog` plugin command, and store its output in a file named `Package.catalog`.

Expand All @@ -160,16 +160,16 @@ $ swift package catalog > Package.catalog

The catalog file must be named `Package.catalog`; Biome parses it (and the `Package.resolved` file generated by the Swift Package Manager) in order to find `swift-json`’s symbolgraphs and DocC archives.

### 2. build [`swift-biome`](https://github.com/tayloraswift/swift-biome)
### 2. build [`swift-biome`](https://github.com/tayloraswift/swift-biome)

[`swift-biome`](https://github.com/tayloraswift/swift-biome) is a normal SPM package. There’s lots of ways to build it.
[`swift-biome`](https://github.com/tayloraswift/swift-biome) is a normal SPM package. There’s lots of ways to build it.

```bash
$ git clone git@github.com:tayloraswift/swift-biome.git
$ git submodule update --init --recursive

$ cd swift-biome
$ swift build -c release
$ cd swift-biome
$ swift build -c release
$ cd ..
```

Expand All @@ -180,7 +180,7 @@ Don’t forget the `git submodule update`!
[`swift-biome`](https://github.com/tayloraswift/swift-biome) includes an executable target called **`preview`**. Pass it the path to the `swift-json` repository (in this example, `..`), and it will start up a server on `localhost:8080`.

```bash
$ cd swift-biome
$ cd swift-biome
$ .build/release/preview --swift 5.6.2 ..
```

Expand Down
28 changes: 14 additions & 14 deletions Snippets/DecodingObjects.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,50 @@ import JSON
import JSONDecoding

// snippet.market-enum-definition
enum Market:String
enum Market:String
{
case spot
case future
case spot
case future
}
extension Market:JSONDecodable
{
}
// snippet.main
func decode(message:String) throws ->
func decode(message:String) throws ->
(
name:String,
market:Market,
isPerpetual:Bool
name:String,
market:Market,
isPerpetual:Bool
)
{
// snippet.parse
let object:JSON.Object = try .init(parsing: message)
// snippet.index
let json:JSON.ObjectDecoder<JSON.Key> = try .init(indexing: object)
// snippet.decode
enum CodingKeys:String
enum CodingKey:String
{
case name
case type
case perpetual
}
return try json["market"].decode(using: CodingKeys.self)
return try json["market"].decode(using: CodingKey.self)
{
// snippet.decode-string
let name:String = try $0[.name].decode(to: String.self)
// snippet.decode-enum
let market:Market = try $0[.type].decode(to: Market.self)
// snippet.decode-elided
let isPerpetual:Bool = try $0[.perpetual]?.decode(to: Bool.self) ?? false
let isPerpetual:Bool = try $0[.perpetual]?.decode(to: Bool.self) ?? false
// snippet.return
return (name, market, isPerpetual)
}
// snippet.hide
}
print(try decode(message:
print(try decode(message:
"""
{
"market":
"market":
{
"name": "BTC-PERP",
"type": "future",
Expand All @@ -54,10 +54,10 @@ print(try decode(message:
}
"""))

print(try decode(message:
print(try decode(message:
"""
{
"market":
"market":
{
"name": "BTC-PERP",
"type": "spot"
Expand Down
6 changes: 3 additions & 3 deletions Sources/JSONDecoding/Decodability/JSONObjectDecodable.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/// A type that can be decoded from a BSON dictionary-decoder.
public
protocol JSONObjectDecodable<CodingKeys>:JSONDecodable
protocol JSONObjectDecodable<CodingKey>:JSONDecodable
{
associatedtype CodingKeys:RawRepresentable<String> & Hashable = JSON.Key
associatedtype CodingKey:RawRepresentable<String> & Hashable = JSON.Key

init(json:JSON.ObjectDecoder<CodingKeys>) throws
init(json:JSON.ObjectDecoder<CodingKey>) throws
}
extension JSONObjectDecodable
{
Expand Down
4 changes: 2 additions & 2 deletions Sources/JSONDecoding/Decoding/JSONScope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ protocol JSONScope
extension JSONScope
{
@inlinable public
func decode<CodingKeys, T>(using _:CodingKeys.Type = CodingKeys.self,
with decode:(JSON.ObjectDecoder<CodingKeys>) throws -> T) throws -> T
func decode<CodingKey, T>(using _:CodingKey.Type = CodingKey.self,
with decode:(JSON.ObjectDecoder<CodingKey>) throws -> T) throws -> T
{
try self.decode { try decode(try .init(json: $0)) }
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/JSONEncoding/Encodability/JSONObjectEncodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
public
protocol JSONObjectEncodable:JSONEncodable
{
associatedtype CodingKeys:RawRepresentable<String> & Hashable
associatedtype CodingKey:RawRepresentable<String> & Hashable

func encode(to json:inout JSON.ObjectEncoder<CodingKeys>)
func encode(to json:inout JSON.ObjectEncoder<CodingKey>)
}
extension JSONObjectEncodable
{
Expand Down
Loading