swift-json

0.3.0

high-performance json parsing and linting for server applications
kelvin13/swift-json

What's New

0.3.0

2022-08-29T18:51:11Z

swift-json v0.3.0 is finally here!

obsoleting json dictionary representations

version 0.2 of swift-json modeled objects (things written in curly braces {}) as dictionaries of String keys and JSON values:

case array([Self])
case object([String: Self])

this is the most obvious representation for a JSON value, it’s convenient for many use cases, and it dovetails nicely with the JSON.array(_:) enumeration case. however, this representation also comes with some functional and performance issues, so we have replaced this representation with a array-based [(key:String, value:JSON)] representation. this matches APIs vended by Dictionary itself.

case object([(key:String, value:Self)])

to convert an object to a Dictionary, use one of the as(_:uniquingKeysWith:) methods on JSON.

ergonomics for high-performance decoding APIs

previously, users of swift-json had to choose between a slow but ergonomic Decodable-based API, and a fast but extremely verbose direct decoding API. swift-json v0.3 comes with a much more ergonomic API for high-performance decoding.

to decode objects, you can now use the LintingDictionary abstraction, and to decode arrays, you can now use swift-json’s Array extensions. in addition to being faster, using these decoding interfaces also gives better error diagnostics when decoding fails.

for an example of how to use LintingDictionary, see its associated snippet.

RawRepresentable convenience APIs

swift-json v0.3 comes with a handful of new convenience APIs for decoding RawRepresentable types directly from JSON messages:

func `as`<StringCoded>(cases: StringCoded.Type) throws -> StringCoded

func `as`<CharacterCoded>(cases: CharacterCoded.Type) throws -> CharacterCoded

func `as`<ScalarCoded>(cases: ScalarCoded.Type) throws -> ScalarCoded

func `as`<IntegerCoded>(cases: IntegerCoded.Type) throws -> IntegerCoded

func `as`<UnsignedIntegerCoded>(cases: UnsignedIntegerCoded.Type) throws -> UnsignedIntegerCoded

avoid working with swift-grammar directly

version 0.2 of swift-json re-exported Grammar’s entire API, and expected users to invoke swift-grammar parsing rules directly. this was done to reduce the amount of “magic” in the parser, and guide users towards the lowest-overhead parsing configurations needed for their use case, which was important back when swift-json was a closed-source library primarily used for fintech applications. however it also made for a very complex API that was hard to learn if you were not already familiar with how swift-grammar works.

version 0.3 of swift-json now comes with a simplified init(parsing:) initializer, which takes UTF-8-encoded input, and just “does the right thing” by default.

ExpressibleBy_Literal conformances

the JSON type now conforms to ExpressibleByArrayLiteral,ExpressibleByBooleanLiteral, ExpressibleByDictionaryLiteral, ExpressibleByExtendedGraphemeClusterLiteral, ExpressibleByStringLiteral, and ExpressibleByUnicodeScalarLiteral.

platform and toolchain support

swift-json supports swift 5.3 ... 5.8 nightly, and has official CI coverage for linux, macOS, iOS, watchOS, tvOS, and windows.

swift-json has a fully-green build matrix on the swift package index.

documentation

we have expanded swift-json’s documentation, and published it on swiftinit.

json
0.3.0

ci build status ci devices build status ci windows build status ci benchmarks status

swift package index versions swift package index platforms

swift-json is a pure-Swift JSON parsing library designed for high-performance, high-throughput server-side applications. When compared using the test data captured.json, swift-json is nearly 7 times faster than Foundation.JSONDecoder (see benchmark source code). This library is powered by swift-grammar!

Importing this module will expose the following top-level symbol(s):

  • enum JSON

example usage

The JSON module in swift-json enables you to express JSON parsing tasks as constructive parsers. This makes the JSON module very flexible without requiring much configuration from users who simply want to parse a JSON message from a remote peer.

To parse a complete JSON message, use its init(parsing:) initializer, or for more flexibility, the JSON.Rule<Location>.Root parsing rule:

BasicDecoding.swift

import JSON 

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

let string:String = 
"""
{"success":true,"value":0.1}
"""
let decoder:JSON = try .init(parsing: string.utf8)
let response:Response = try .init(from: decoder)

print(response)
$ swift run BasicDecoding
Response(success: true, value: Decimal(units: 1, places: 1))

The rule is called “Root” because it will match only complete JSON messages (objects or arrays). Like most swift-grammar-based parsers, JSON.Rule is generic over its input, which means you can parse directly from some Collection of UInt8.

swift-json’s constructive parsing engine also allows you to get diagnostics for invalid JSON messages:

Diagnostics.swift

import Grammar
import JSON

let invalid:String = 
"""
{"success":true,value:0.1}
"""
do 
{
    let _:JSON = try JSON.Rule<String.Index>.Root.parse(diagnosing: invalid.utf8)
}
catch let error as ParsingError<String.Index> 
{
    let debug:String = error.annotate(source: invalid, 
        line: String.init(_:), newline: \.isNewline)
    print(debug)
}
$ swift run Diagnostics
Grammar.Expected<Grammar.Encoding<String.Index, UInt8>.Quote>: expected construction by rule 'Quote'
{"success":true,value:0.1}
                ^
note: expected pattern 'Grammar.Encoding<String.Index, UInt8>.Quote'
{"success":true,value:0.1}
                ^
note: while parsing value of type 'String' by rule 'JSON.Rule<String.Index>.StringLiteral'
{"success":true,value:0.1}
                ^
note: while parsing value of type '((), (key: String, value: JSON))' 
by rule '(Grammar.Pad<Grammar.Encoding<String.Index, UInt8>.Comma, 
JSON.Rule<String.Index>.Whitespace>, JSON.Rule<String.Index>.Object.Item)'
{"success":true,value:0.1}
               ^~
note: while parsing value of type 'Array<(key: String, value: JSON)>' by rule 'JSON.Rule<String.Index>.Object'
{"success":true,value:0.1}
^~~~~~~~~~~~~~~~~
note: while parsing value of type 'JSON' by rule 'JSON.Rule<String.Index>.Root'
{"success":true,value:0.1}
^~~~~~~~~~~~~~~~~

You can be more selective about the form of the JSON you expect to receive by using one of the library’s subrules:

The JSON module supports parsing arbitrary JSON fragments using the JSON.Rule<Location>.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

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

let package = Package(
    ...
    dependencies: 
    [
        // other dependencies
        .package(url: "https://github.com/kelvin13/swift-json", from: "0.2.2"),
    ],
    targets: 
    [
        .target(name: "example", 
            dependencies: 
            [
                .product(name: "JSON", package: "swift-json"),
                // other dependencies
            ]),
        // other targets
    ]
)

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.

1. gather the documentation files

swift-json uses the swift-package-catalog plugin to gather its documentation.

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

$ 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

swift-biome is a normal SPM package. There’s lots of ways to build it.

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

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

Don’t forget the git submodule update!

3. run the preview server

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.

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

The --swift 5.6.2 option specifies the version of the standard library that the Biome compiler should link against.

Note: if you run the preview tool from outside the swift-biome repository, you will need to specify the path to the resources (sub)module. For example, if you did not cd into swift-biome, you would have to add the option --resources swift-biome/resources.

Navigate to http://127.0.0.1:8080/reference/swift-json in a browser to view the documentation. Make sure the scheme is http:// and not https://.

Description

  • Swift Tools 5.5.0
View More Packages from this Author

Dependencies

Last updated: Thu Mar 16 2023 13:26:28 GMT-0500 (GMT-05:00)