Contents

tannerdsilva/quickjson

QuickJSON is an easy, efficient, and uncompromising integration of the yyjson parsing library.

Usage

Basic encode/decode

import QuickJSON

struct User: Codable {
    let id: Int
    let name: String
}

let user = User(id: 1, name: "Tanner")
let bytes = try QuickJSON.encode(user)                     // [UInt8]
let roundTrip = try QuickJSON.decode(User.self, from: bytes)

decode accepts any Collection of UInt8 (arrays, slices, lazy filters, Data…), or a raw pointer:

let fromSlice = try QuickJSON.decode(User.self, from: bytes[0..<4])
let fromPointer = try bytes.withUnsafeBytes { raw in
    try QuickJSON.decode(User.self, from: raw.baseAddress!, size: raw.count)
}

Handler-based decoding

When you want dynamic parsing — reading keys directly, custom control flow — use the handler overloads. The closure receives a Swift.Decoder and its return value is returned transparently:

let keys = try QuickJSON.decode(from: bytes) { decoder in
    let container = try decoder.container(keyedBy: UserKeys.self)
    return Set(container.allKeys)
}

Preallocated memory

Memory.Region allocates a fixed pool once; both encode and decode can run inside it:

let region = try Memory.Region(maximumReadingSize: 1024)
let decoded = try QuickJSON.decode(User.self, from: bytes, memory: .preallocated(region))

// resolve the recommended pool size for a known input size up front:
let size = Memory.Region.recommendedReadBufferSize(maximumInput: 4096, flags: .init())

Flags

Encoding and decoding flags mirror yyjson's option sets:

let pretty = try QuickJSON.encode(user, flags: [.pretty])            // human readable
let permissive = try QuickJSON.decode(
    User.self, from: bytes,
    flags: [.allowComments, .allowTrailingCommas]
)

API overview

| symbol | purpose | | --- | --- | | QuickJSON.encode(:flags:memory:logLevel:) | encode any Encodable value to [UInt8] | | QuickJSON.decode(:from:flags:memory:logLevel:) | decode any Decodable value from a collection or raw pointer | | QuickJSON.decode(from:flags:memory:logLevel:_:) | decode with a handler function | | Encoding.Flags | write options: .pretty, .escapeUnicode, .escapeSlashes, .allowInfAndNan, .infAndNanAsNull, .allowInvalidUnicode, .prettyTwoSpaces | | Decoding.Flags | read options: .inSitu, .stopWhenDone, .allowTrailingCommas, .allowComments, .allowInfAndNaN, .allowInvalidUnicode | | Memory.Configuration / Memory.Region | automatic or pooled allocation | | Decoding.Error | typed decoding errors (see below) |

Error model

Encoding failures throw Encoding.Error:

| error | thrown when | | --- | --- | | assignmentError | a value could not be attached to the document | | memoryAllocationFailure | yyjson could not allocate the document or output buffer |

Decoding failures throw Decoding.Error:

| error | thrown when | | --- | --- | | valueTypeMismatch(ValueTypeMismatchInfo) | the JSON value is a different type than requested | | numberOutOfRange(requestedType:value:) | a numeric value does not fit in the requested type | | notFound | a requested key is not present in the object | | contentOverflow | an unkeyed container is read past its end | | documentParseError(ParseInfo) | the JSON document could not be parsed | | documentRootError | the parsed document has no root value |

Logging

Logging is compiled in and off by default: every call no-ops its log statements unless a logLevel other than .critical is supplied.

let bytes = try QuickJSON.encode(user, logLevel: .debug)

The default loggers (Encoding.logger, Decoding.logger) can be replaced with custom Logger instances; per-call log levels apply on top.

Performance

Measured with the bundled benchmark suite (swift test --filter BenchmarkTests) against Foundation's JSONEncoder/JSONDecoder, yyjson 0.12, Swift 6.3, Apple Silicon (nanoseconds per operation):

| workload | QuickJSON encode | JSONEncoder encode | QuickJSON decode | JSONDecoder decode | | --- | --- | --- | --- | --- | | small model | 779 | 903 | 846 | 1,272 | | nested object | 1,251 | 1,523 | 1,338 | 1,957 | | array of 100 | 54,772 | 62,833 | 46,261 | 62,437 | | unicode-heavy strings | 5,334 | 4,665 | 5,325 | 4,172 |

QuickJSON is roughly 1.1–1.2× faster encoding and 1.3–1.5× faster decoding than Foundation on structural payloads. On string-dominated payloads Foundation is faster (~0.8–0.9×) — measure your workload before assuming a win.

Compatibility

Platforms

  • macOS 13+
  • iOS 13+ and variants (tvOS 13+, watchOS 7+, visionOS 1+)
  • Linux

Swift

This package requires a Swift 6 toolchain (the manifest is swift-tools-version: 6.0). The package targets Swift language mode 5; consumer builds in strict concurrency mode interoperate with the Sendable-conforming public types.

Dependencies

  • yyjson: the high-performance JSON core.
  • swift-log: used for optional runtime logging. Log output is disabled by default, so the dependency adds no output overhead unless a logLevel is supplied.

Versioning

QuickJSON follows Semantic Versioning 2.0.0. See CHANGELOG.md for the release history.

License

QuickJSON and yyjson are both available under the MIT license.

Package Metadata

Repository: tannerdsilva/quickjson

Default branch: master

README: README.md