Skip to content

Adds initializers to support parsing JSON directly from a Span.#107

Merged
tayloraswift merged 3 commits intorarestype:masterfrom
t089:span
Mar 13, 2026
Merged

Adds initializers to support parsing JSON directly from a Span.#107
tayloraswift merged 3 commits intorarestype:masterfrom
t089:span

Conversation

@t089
Copy link
Contributor

@t089 t089 commented Mar 6, 2026

Hi @tayloraswift, I found those additions to be quite useful, to avoid unnecessary copies of the JSON buffer to be parsed. Would you want to take them? Currently they need to use the unsafe APIs because the parser requires Collection which span does not provide.

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the JSON parsing capabilities by introducing initializers that allow direct parsing from memory spans. This change is designed to optimize performance by eliminating the need to copy JSON buffer data, thereby reducing memory allocations and improving processing efficiency, particularly for large JSON payloads.

Highlights

  • New JSON Parsing Initializers: Introduced new initializers across JSON.Array, JSON.Node, and JSON.Object to enable direct parsing from Span<UInt8> and RawSpan types.
  • Performance Optimization: These additions aim to improve performance by allowing zero-copy parsing of JSON data directly from memory buffers, avoiding unnecessary data duplication.
  • Unsafe API Usage: The new initializers leverage Swift's unsafe APIs (specifically withUnsafeBufferPointer and withUnsafeBytes) because Span types do not conform to the Collection protocol required by the underlying parser.
Changelog
  • Sources/JSONParsing/JSON.Array (ext).swift
    • Added init(parsing span: Span<UInt8>) throws to parse JSON arrays directly from a Span<UInt8>.
    • Added init(parsing span: RawSpan) throws to parse JSON arrays directly from a RawSpan.
    • Adjusted whitespace formatting within the LosslessStringConvertible extension.
  • Sources/JSONParsing/JSON.Node (ext).swift
    • Added init(parsing span: Span<UInt8>) throws to parse complete JSON messages directly from a Span<UInt8>.
    • Added init(parsing span: RawSpan) throws to parse complete JSON messages directly from a RawSpan.
  • Sources/JSONParsing/JSON.Object (ext).swift
    • Added init(parsing span: Span<UInt8>) throws to parse JSON objects directly from a Span<UInt8>.
    • Added init(parsing span: RawSpan) throws to parse JSON objects directly from a RawSpan.
Activity
  • The author, t089, proposed these additions to @tayloraswift, highlighting their utility in avoiding unnecessary JSON buffer copies and noting the use of unsafe APIs due to Collection protocol limitations with Span.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces useful initializers for parsing JSON directly from Span and RawSpan types, aiming to improve performance by avoiding unnecessary buffer copies. No specific security vulnerabilities were found in the changes. However, the implementation could benefit from improved code maintainability by reducing duplication. The newly added initializers in the modified files share a similar structure, and I've provided suggestions to refactor this repeated logic into private generic helper methods within each extension for conciseness and easier maintenance.

Comment on lines +9 to +24
/// Attempts to parse a JSON array from a span
public init(parsing span: Span<UInt8>) throws {
self.init(
try span.withUnsafeBufferPointer { buffer in
try JSON.NodeRule<Int>.Array.parse(buffer)
}
)
}
/// Attempts to parse a JSON array from a raw span
public init(parsing span: RawSpan) throws {
self.init(
try span.withUnsafeBytes { buffer in
try JSON.NodeRule<Int>.Array.parse(buffer)
}
)
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new initializers for Span<UInt8> and RawSpan contain duplicated logic. You can reduce this by extracting the parsing logic into a private, generic, static method. This helper can then be passed directly to withUnsafeBufferPointer and withUnsafeBytes, making the code more concise and maintainable.

    /// Attempts to parse a JSON array from a span
    public init(parsing span: Span<UInt8>) throws {
        self.init(try span.withUnsafeBufferPointer(Self._parse(from:)))
    }
    /// Attempts to parse a JSON array from a raw span
    public init(parsing span: RawSpan) throws {
        self.init(try span.withUnsafeBytes(Self._parse(from:)))
    }

    private static func _parse<C>(from buffer: C) throws -> [JSON.Node]
        where C: Collection, C.Element == UInt8, C.Index == Int
    {
        try JSON.NodeRule<Int>.Array.parse(buffer)
    }

Comment on lines +11 to +23
/// ``Object``) from a span.
public init(parsing span: Span<UInt8>) throws {
self = try span.withUnsafeBufferPointer { buffer in
try JSON.RootRule<Int>.parse(buffer)
}
}
/// Attempts to parse a complete JSON message (either an ``Array`` or an
/// ``Object``) from a raw span.
public init(parsing span: RawSpan) throws {
self = try span.withUnsafeBytes { buffer in
try JSON.RootRule<Int>.parse(buffer)
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new initializers for Span<UInt8> and RawSpan contain duplicated logic. You can reduce this by extracting the parsing logic into a private, generic, static method. This helper can then be passed directly to withUnsafeBufferPointer and withUnsafeBytes, making the code more concise and maintainable.

Suggested change
/// ``Object``) from a span.
public init(parsing span: Span<UInt8>) throws {
self = try span.withUnsafeBufferPointer { buffer in
try JSON.RootRule<Int>.parse(buffer)
}
}
/// Attempts to parse a complete JSON message (either an ``Array`` or an
/// ``Object``) from a raw span.
public init(parsing span: RawSpan) throws {
self = try span.withUnsafeBytes { buffer in
try JSON.RootRule<Int>.parse(buffer)
}
}
/// ``Object``) from a span.
public init(parsing span: Span<UInt8>) throws {
self = try span.withUnsafeBufferPointer(Self._parse(from:))
}
/// Attempts to parse a complete JSON message (either an ``Array`` or an
/// ``Object``) from a raw span.
public init(parsing span: RawSpan) throws {
self = try span.withUnsafeBytes(Self._parse(from:))
}
private static func _parse<C>(from buffer: C) throws -> JSON.Node
where C: Collection, C.Element == UInt8, C.Index == Int
{
try JSON.RootRule<Int>.parse(buffer)
}

Comment on lines +12 to +27
/// Attempts to parse a JSON object from a span.
public init(parsing span: Span<UInt8>) throws {
self.init(
try span.withUnsafeBufferPointer { buffer in
try JSON.NodeRule<Int>.Object.parse(buffer)
}
)
}
/// Attempts to parse a JSON object from a raw span.
public init(parsing span: RawSpan) throws {
self.init(
try span.withUnsafeBytes { buffer in
try JSON.NodeRule<Int>.Object.parse(buffer)
}
)
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new initializers for Span<UInt8> and RawSpan contain duplicated logic. You can reduce this by extracting the parsing logic into a private, generic, static method. This helper can then be passed directly to withUnsafeBufferPointer and withUnsafeBytes, making the code more concise and maintainable.

    /// Attempts to parse a JSON object from a span.
    public init(parsing span: Span<UInt8>) throws {
        self.init(try span.withUnsafeBufferPointer(Self._parse(from:)))
    }
    /// Attempts to parse a JSON object from a raw span.
    public init(parsing span: RawSpan) throws {
        self.init(try span.withUnsafeBytes(Self._parse(from:)))
    }

    private static func _parse<C>(from buffer: C) throws -> [(key: JSON.Key, value: JSON.Node)]
        where C: Collection, C.Element == UInt8, C.Index == Int
    {
        try JSON.NodeRule<Int>.Object.parse(buffer)
    }

@tayloraswift
Copy link
Member

should we limit this to just RawSpan?

@t089
Copy link
Contributor Author

t089 commented Mar 13, 2026

should we limit this to just RawSpan?

Yes, we should. Done!

@tayloraswift tayloraswift merged commit 3cdb9f4 into rarestype:master Mar 13, 2026
8 checks passed
tayloraswift added a commit that referenced this pull request Mar 15, 2026
Adds initializers to support parsing JSON directly from a Span.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants