YYJSON

0.5.0

A fast JSON library for Swift, powered by yyjson
mattt/swift-yyjson

What's New

0.5.0

2026-02-06T12:14:13Z

What's Changed

  • Add YYJSONSerialization formatting options by @alexey1312 in #13
  • Add direct write pass to YYJSONSerialization to bypass Foundation round‑tripping by @mattt in #15
  • Add Linux support by @alexey1312 in #16
  • Rename prettyPrintedTwoSpaces to indentationTwoSpaces by @mattt in #17
  • Add encoder option to write infinity and NaN as null by @mattt in #18

Full Changelog: 0.4.0...0.5.0

YYJSON

A fast JSON library for Swift, powered by yyjson.

YYJSON provides API-compatible alternatives for JSONEncoder, JSONDecoder, and JSONSerialization. It supports configurable compile-time options via Swift package traits for further optimization.

Benchmarks

YYJSON delivers significant performance improvements over Foundation's JSON APIs. These benchmarks compare parsing times using standard JSON test fixtures from nativejson-benchmark.

Fixture YYJSON Foundation Speedup
twitter.json (~632KB) ~180 μs ~2.9 ms ~16×
citm_catalog.json (~1.7MB) ~425 μs ~4.3 ms ~10×
canada.json (~2.2MB) ~2.3 ms ~36.0 ms ~16×
tokenizer.json (~11MB) ~6.5 ms ~57.0 ms ~9×

YYJSON also uses significantly less memory. Parsing twitter.json requires only 3 allocations compared to over 6,600 for Foundation, with peak memory of 19 MB versus up to 378 MB. For maximum efficiency, in-place parsing eliminates allocations entirely by operating directly on the input buffer.

The performance advantage is most pronounced for large files, access-heavy workloads where YYJSON's value-based API avoids repeated type casting, and number-heavy data like GeoJSON that benefits from optimized floating-point parsing.

For detailed methodology and additional benchmarks, see swift-yyjson-benchmark.

Raw Results
swift package benchmark --format markdown --filter "Fixture/.+/Parse/.+" --time-units microseconds
Host 'MacBook-Pro.local' with 16 'arm64' processors with 48 GB memory, running:
Darwin Kernel Version 25.2.0: Tue Nov 18 21:09:56 PST 2025; root:xnu-12377.61.12~1/RELEASE_ARM64_T6041

Fixture/canada.json/Parse/Foundation

Metric p0 p25 p50 p75 p90 p99 p100 Samples
Instructions (M) * 308 308 308 308 309 312 312 85
Malloc (total) (K) * 167 167 167 167 167 167 167 85
Memory (resident peak) (M) 17 148 274 394 478 524 524 85
Throughput (# / s) (#) 88 85 85 84 83 82 82 85
Time (total CPU) (μs) * 11425 11731 11821 11969 12034 12234 12234 85
Time (wall clock) (μs) * 11419 11723 11821 11969 12034 12227 12227 85

Fixture/canada.json/Parse/YYJSON

Metric p0 p25 p50 p75 p90 p99 p100 Samples
Instructions (M) * 35 35 35 35 35 35 35 790
Malloc (total) * 3 3 3 3 3 3 3 790
Memory (resident peak) (M) 17 22 22 22 22 22 22 790
Throughput (# / s) (#) 861 810 802 795 787 760 745 790
Time (total CPU) (μs) * 1163 1236 1249 1261 1274 1318 1344 790
Time (wall clock) (μs) * 1162 1234 1247 1258 1271 1316 1342 790

Fixture/citm_catalog.json/Parse/Foundation

Metric p0 p25 p50 p75 p90 p99 p100 Samples
Instructions (M) * 73 73 73 73 73 73 74 297
Malloc (total) (K) * 14 14 14 14 14 14 14 297
Memory (resident peak) (M) 18 90 161 230 276 301 301 297
Throughput (# / s) (#) 312 304 301 296 284 276 273 297
Time (total CPU) (μs) * 3205 3293 3330 3383 3521 3633 3660 297
Time (wall clock) (μs) * 3203 3291 3328 3381 3518 3629 3659 297

Fixture/citm_catalog.json/Parse/YYJSON

Metric p0 p25 p50 p75 p90 p99 p100 Samples
Instructions (K) * 9850 9855 9855 9855 9855 9871 10528 2871
Malloc (total) * 3 3 3 3 3 3 3 2871
Memory (resident peak) (M) 18 22 22 22 22 22 22 2871
Throughput (# / s) (#) 3253 3075 3025 2973 2929 2801 2590 2871
Time (total CPU) (μs) * 309 327 332 338 343 359 392 2871
Time (wall clock) (μs) * 307 325 331 336 342 357 386 2871

Fixture/twitter.json/Parse/Foundation

Metric p0 p25 p50 p75 p90 p99 p100 Samples
Instructions (M) * 44 44 44 44 44 44 44 501
Malloc (total) * 6636 6637 6637 6637 6637 6637 6637 501
Memory (resident peak) (M) 18 108 198 285 342 374 378 501
Throughput (# / s) (#) 531 514 510 505 492 455 436 501
Time (total CPU) (μs) * 1887 1946 1964 1985 2032 2198 2296 501
Time (wall clock) (μs) * 1883 1945 1962 1982 2032 2198 2294 501

Fixture/twitter.json/Parse/YYJSON

Metric p0 p25 p50 p75 p90 p99 p100 Samples
Instructions (K) * 3509 3510 3510 3510 3510 3527 3941 6785
Malloc (total) * 3 3 3 3 3 3 3 6785
Memory (resident peak) (M) 17 19 19 19 19 19 19 6785
Throughput (# / s) (#) 8544 8179 7791 7399 7267 6687 2383 6785
Time (total CPU) (μs) * 118 124 130 137 139 152 339 6785
Time (wall clock) (μs) * 117 122 128 135 138 150 420 6785

Fixture/tokenizer.json/Parse/Foundation

Metric p0 p25 p50 p75 p90 p99 p100 Samples
Instructions (M) * 1212 1213 1213 1213 1213 1215 1215 18
Malloc (total) (K) * 382 382 382 382 382 382 382 18
Memory (resident peak) (M) 74 158 242 344 407 430 430 18
Throughput (# / s) (#) 18 18 17 17 17 17 17 18
Time (total CPU) (μs) * 54226 56001 56984 57541 58950 59070 59070 18
Time (wall clock) (μs) * 54202 56001 56951 57541 58917 59050 59050 18

Fixture/tokenizer.json/Parse/YYJSON

Metric p0 p25 p50 p75 p90 p99 p100 Samples
Instructions (M) * 105 105 105 106 106 106 106 153
Malloc (total) * 4 4 4 4 4 4 4 153
Memory (resident peak) (M) 28 52 52 52 52 52 52 153
Throughput (# / s) (#) 158 154 153 153 152 147 127 153
Time (total CPU) (μs) * 6316 6480 6525 6562 6607 6754 7863 153
Time (wall clock) (μs) * 6315 6476 6521 6554 6599 6816 7857 153

Requirements

  • Swift 6.1+ / Xcode 16+
  • macOS 10.15+ / iOS 13+ / tvOS 13+ / watchOS 6+ / visionOS 1+

Installation

Swift Package Manager

Add the following to your Package.swift file:

dependencies: [
    .package(url: "https://github.com/mattt/swift-yyjson.git", from: "0.3.0")
]

Usage

Decoding with Codable

Use YYJSONDecoder as an alternative to JSONDecoder:

import YYJSON

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

let json = #"{"id": 1, "name": "Alice", "email": "alice@example.com"}"#
let data = json.data(using: .utf8)!

let decoder = YYJSONDecoder()
let user = try decoder.decode(User.self, from: data)
print(user.name) // "Alice"

YYJSONDecoder supports the same decoding strategies as JSONDecoder:

let decoder = YYJSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
decoder.dataDecodingStrategy = .base64

JSON5 Support

Enable JSON5 parsing for more flexible input:

let decoder = YYJSONDecoder()
decoder.allowsJSON5 = true  // Enable all JSON5 features

Or configure individual JSON5 features:

decoder.allowsJSON5 = .init(
    trailingCommas: true,   // Allow [1, 2, 3,]
    comments: true,         // Allow // and /* */ comments
    infAndNaN: true,        // Allow Infinity and NaN literals
    singleQuotedStrings: true  // Allow 'single quotes'
)

Note

JSON5 support is unavailable when the strictStandardJSON trait is enabled. The allowsJSON5 property and JSON5DecodingOptions type are conditionally compiled and will not be available at compile time.

Encoding with Codable

Use YYJSONEncoder as an alternative to JSONEncoder:

import YYJSON

let user = User(id: 1, name: "Alice", email: "alice@example.com")

let encoder = YYJSONEncoder()
let data = try encoder.encode(user)
print(String(data: data, encoding: .utf8)!)
// {"id":1,"name":"Alice","email":"alice@example.com"}

Configure output formatting:

var encoder = YYJSONEncoder()
encoder.writeOptions = [.prettyPrinted, .escapeUnicode]

YYJSONEncoder supports date encoding strategies:

var encoder = YYJSONEncoder()
encoder.dateEncodingStrategy = .iso8601
// Or: .secondsSince1970, .millisecondsSince1970, .formatted(formatter), .custom(closure)

DOM-Style Access

Parse JSON and access values directly without defining types:

import YYJSON

let json = #"{"users": [{"name": "Alice"}, {"name": "Bob"}]}"#
let value = try YYJSONValue(string: json)

// Access nested values with subscripts
if let name = value["users"]?[0]?["name"]?.string {
    print(name) // "Alice"
}

In-Place Parsing

For maximum performance with large JSON files, use in-place parsing to avoid copying the input data:

var data = try Data(contentsOf: fileURL)
let json = try YYJSONValue.parseInPlace(consuming: &data)
// `data` is now consumed and should not be used

In-place parsing allows yyjson to parse directly within the input buffer, avoiding memory allocation for string storage. The inout parameter makes it clear that the data is consumed by this operation.

Note

For most use cases, the standard YYJSONValue(data:) initializer is sufficient. Use in-place parsing only when performance is critical and you can accept the ownership semantics.

JSONSerialization Alternative

Use YYJSONSerialization with the same API as Foundation's JSONSerialization:

import YYJSON

let json = #"{"message": "Hello, World!"}"#
let data = json.data(using: .utf8)!

let object = try YYJSONSerialization.jsonObject(with: data)
if let dict = object as? [String: Any] {
    print(dict["message"] as? String ?? "") // "Hello, World!"
}

Configure output formatting with WritingOptions:

// Pretty printing with 2-space indent (useful for Xcode asset catalogs)
let data = try YYJSONSerialization.data(
    withJSONObject: dict,
    options: [.indentationTwoSpaces, .sortedKeys]
)

// ASCII-only output with trailing newline
let data = try YYJSONSerialization.data(
    withJSONObject: dict,
    options: [.escapeUnicode, .newlineAtEnd]
)

Available writing options:

  • .fragmentsAllowed — Allow top-level values that aren't arrays or dictionaries
  • .prettyPrinted — Pretty print with 4-space indent
  • .sortedKeys — Sort dictionary keys lexicographically
  • .withoutEscapingSlashes — Don't escape / as \/
  • .indentationTwoSpaces — Configure pretty printing to use 2-space indent (implies .prettyPrinted)
  • .escapeUnicode — Escape non-ASCII characters as \uXXXX
  • .newlineAtEnd — Add trailing newline \n

Non-standard options (unavailable when strictStandardJSON trait is enabled):

  • .allowInfAndNaN — Write Infinity and NaN literals
  • .infAndNaNAsNull — Write Infinity and NaN as null (takes precedence)

Read and Write Options

Reading Options

Configure parsing behavior with YYJSONReadOptions:

let value = try YYJSONValue(data: data, options: [.allowComments, .allowTrailingCommas])

Available options:

  • .stopWhenDone — Stop after first complete JSON document
  • .numberAsRaw — Read all numbers as raw strings
  • .allowInvalidUnicode — Allow reading invalid unicode
  • .bigNumberAsRaw — Read big numbers as raw strings

Non-standard options (unavailable when strictStandardJSON trait is enabled):

  • .allowTrailingCommas — Allow [1, 2, 3,]
  • .allowComments — Allow // and /* */ comments
  • .allowInfAndNaN — Allow Infinity, -Infinity, NaN
  • .allowBOM — Allow UTF-8 BOM
  • .allowExtendedNumbers — Allow hex, leading ., trailing ., leading +
  • .allowExtendedEscapes — Allow \a, \e, \v, \xNN, etc.
  • .allowExtendedWhitespace — Allow extended whitespace characters
  • .allowSingleQuotedStrings — Allow 'single quotes'
  • .allowUnquotedKeys — Allow {key: value}
  • .json5 — Enable all JSON5 features

Writing Options

Configure output with YYJSONWriteOptions:

var encoder = YYJSONEncoder()
encoder.writeOptions = [.prettyPrinted, .escapeSlashes]

Available options:

  • .prettyPrinted — Pretty print with 4-space indent
  • .indentationTwoSpaces — Pretty print with 2-space indent (implies .prettyPrinted)
  • .escapeUnicode — Escape non-ASCII as \uXXXX
  • .escapeSlashes — Escape / as \/
  • .allowInvalidUnicode — Allow invalid unicode when encoding
  • .newlineAtEnd — Add trailing newline
  • .sortedKeys — Sort object keys lexicographically

Non-standard options (unavailable when strictStandardJSON trait is enabled):

  • .allowInfAndNaN — Write Infinity and NaN literals
  • .infAndNaNAsNull — Write Infinity and NaN as null (takes precedence)

Package Traits

Customize the underlying yyjson library at compile time using package traits:

.package(
    url: "https://github.com/mattt/swift-yyjson.git",
    from: "0.3.0",
    traits: ["noWriter", "strictStandardJSON"]
)

By default, no traits are enabled — you get full functionality with all features and validations included. Enable traits only when you have specific size or performance requirements.

Note

When traits are enabled, the corresponding Swift APIs are conditionally compiled and become unavailable at compile time. For example, enabling the noReader trait makes unavailable YYJSONDecoder, YYJSONValue, and YYJSONSerialization.jsonObject(with:options:). Similarly, enabling the noWriter trait makes unavailable YYJSONEncoder and YYJSONSerialization.data(withJSONObject:options:).

noReader

Disables JSON reader functionality at compile-time (functions with "read" in their name). Reduces binary size by about 60%. Use this if your application only needs to write JSON, not parse it.

When this trait is enabled, the following APIs become unavailable:

  • YYJSONDecoder
  • YYJSONValue, YYJSONObject, YYJSONArray
  • YYJSONSerialization.jsonObject(with:options:)

noWriter

Disables JSON writer functionality at compile-time (functions with "write" in their name). Reduces binary size by about 30%. Use this if your application only needs to parse JSON, not generate it.

When this trait is enabled, the following APIs become unavailable:

  • YYJSONEncoder
  • YYJSONSerialization.data(withJSONObject:options:)

noIncrementalReader

Disables the incremental JSON reader at compile-time. Use this if you don't need to parse JSON in streaming/chunked mode.

noUtilities

Disables support for JSON Pointer, JSON Patch, and JSON Merge Patch. Use this if you don't need these utilities for querying or modifying JSON documents.

noFastFloatingPoint

Disables yyjson's fast floating-point number conversion and uses libc's strtod/snprintf instead. Reduces binary size by about 30%, but significantly slows down floating-point read/write speed. Use this only if binary size is critical and you don't process many floating-point values.

strictStandardJSON

Disables non-standard JSON features at compile-time (such as allowing comments, trailing commas, or infinity/NaN values). Reduces binary size by about 10% and slightly improves performance. Use this if you only need to handle strictly conformant JSON.

When this trait is enabled, the following APIs become unavailable:

  • YYJSONReadOptions.allowTrailingCommas
  • YYJSONReadOptions.allowComments
  • YYJSONReadOptions.allowInfAndNaN
  • YYJSONReadOptions.allowBOM
  • YYJSONReadOptions.allowExtendedNumbers
  • YYJSONReadOptions.allowExtendedEscapes
  • YYJSONReadOptions.allowExtendedWhitespace
  • YYJSONReadOptions.allowSingleQuotedStrings
  • YYJSONReadOptions.allowUnquotedKeys
  • YYJSONReadOptions.json5
  • YYJSONWriteOptions.allowInfAndNaN
  • YYJSONWriteOptions.infAndNaNAsNull
  • YYJSONDecoder.allowsJSON5
  • JSON5DecodingOptions
  • YYJSONSerialization.ReadingOptions.json5Allowed

noUTF8Validation

Disables UTF-8 validation at compile-time. Improves performance for non-ASCII strings by about 3% to 7%. Use this only if all input strings are guaranteed to be valid UTF-8.

Caution

If this trait is enabled while passing invalid UTF-8 data, parsing errors may be silently ignored, strings may merge unexpectedly, or out-of-bounds memory access may occur.

Differences from Foundation

YYJSONDecoder and YYJSONEncoder are designed to be API-compatible with Foundation's JSONDecoder and JSONEncoder for common use cases. However, there are some differences:

  • Error types: Throws YYJSONError instead of DecodingError/EncodingError. YYJSONSerialization also throws YYJSONError rather than NSError.

  • Encoder strategies: YYJSONEncoder does not yet support keyEncodingStrategy or nonConformingFloatEncodingStrategy

  • Output formatting: Uses writeOptions instead of outputFormatting

  • Number precision: yyjson parses numbers as 64-bit integers or doubles; extremely large integers may lose precision

Thread Safety

  • YYJSONDecoder and YYJSONEncoder are value types and safe to use from multiple threads, as long as each encode/decode call is not shared concurrently.
  • YYJSONValue, YYJSONObject, and YYJSONArray are safe to share across threads for read-only access; they wrap an immutable yyjson document.
  • The number property on YYJSONValue returns a Double. For exact representation of very large numbers, parse using .bigNumberAsRaw and read them as strings.

License

This project is available under the MIT license. See the LICENSE file for more info.

The underlying yyjson library is also available under the MIT license.

Description

  • Swift Tools 6.1.0
View More Packages from this Author

Dependencies

  • None
Last updated: Mon Feb 09 2026 00:14:55 GMT-1000 (Hawaii-Aleutian Standard Time)