Contents

mhayes853/swift-stream-parsing

A Swift toolkit for type-safe incremental parsing.

Overview

JSONDecoder and Codable are powerful tools when you need to decode structured JSON bytes, however both of those tools require the entire data payload to be present at decode time.

This is especially problematic for applications such as streaming structured data from LLMs. For example, the FoundationModels framework has its own set of interfaces for incrementally streaming structured data.

This library offers a dedicated interface for incremental parsing with built-in JSON support.

Quick Start

First, you create a struct that uses the @StreamParseable macro, and then you can begin parsing!

import StreamParsing

@StreamParseable
struct Profile {
  var id: Int
  var name: String
  var isActive: Bool
}

let json = """
{
  "id": 4,
  "name": "Blob",
  "isActive": true
}
"""

let partials: [Profile.Partial] = try json.utf8
  .partials(of: Profile.Partial.self, from: .json())

for partial in partials {
  print(partial)
}

// Prints:
// Profile.Partial(id: nil, name: nil, isActive: nil)
// ...
// Profile.Partial(id: Optional(4), name: nil, isActive: nil)
// ...
// Profile.Partial(id: Optional(4), name: Optional("B"), isActive: nil)
// Profile.Partial(id: Optional(4), name: Optional("Bl"), isActive: nil)
// Profile.Partial(id: Optional(4), name: Optional("Blo"), isActive: nil)
// Profile.Partial(id: Optional(4), name: Optional("Blob"), isActive: nil)
// ...
// Profile.Partial(id: Optional(4), name: Optional("Blob"), isActive: Optional(true))

The @StreamParseable macro generates a Partial struct with all optional members.

extension Profile: StreamParsingCore.StreamParseable {
  struct Partial: StreamParsingCore.StreamParseableValue,
    StreamParsingCore.StreamParseable {
    typealias Partial = Self

    var id: Int.Partial?
    var name: String.Partial?
    var isActive: Bool.Partial?

    init(
      id: Int.Partial? = nil,
      name: String.Partial? = nil,
      isActive: Bool.Partial? = nil
    ) {
      self.id = id
      self.name = name
      self.isActive = isActive
    }

    static func initialParseableValue() -> Self {
      Self()
    }

    static func registerHandlers(
      in handlers: inout some StreamParsingCore.StreamParserHandlers<Self>
    ) {
      handlers.registerKeyedHandler(forKey: "id", \.id)
      handlers.registerKeyedHandler(forKey: "name", \.name)
      handlers.registerKeyedHandler(forKey: "isActive", \.isActive)
    }
  }
}

Additionally, all stored members on an @StreamParseable must also conform to the StreamParseable protocol. Naturally, the @StreamParseable macro handles the protocol conformance for you.

You can also parse partials from an AsyncSequence of bytes or byte chunks.

struct AsyncJSONBytesSequence: AsyncSequence {
  typealias Element = UInt8
  
  // ...
}

let partials = AsyncJSONBytesSequence(...)
  .partials(of: Profile.Partial.self, from: .json())
for try await profilePartial in partials {
  print(profilePartial)
}

Parsers

The library comes with built-in JSON and YAML parsers. You can pass a custom configuration to either parser to customize key decoding behavior.

JSON

let configuration = JSONStreamParserConfiguration(
  syntaxOptions: [.comments, .trailingCommas],
  keyDecodingStrategy: .convertFromSnakeCase
)

let partials: [Profile.Partial] = try json.utf8
  .partials(of: Profile.Partial.self, from: .json(configuration: configuration))

YAML

let yaml = """
id: 4
name: Blob
isActive: true
"""

let partials: [Profile.Partial] = try yaml.utf8
  .partials(of: Profile.Partial.self, from: .yaml())

let configuration = YAMLStreamParserConfiguration(
  keyDecodingStrategy: .convertFromSnakeCase
)

let snakeCaseYAML = """
id: 4
name: Blob
is_active: true
"""

let partials = try snakeCaseYAML.utf8.partials(
  of: Profile.Partial.self,
  from: .yaml(configuration: configuration)
)

Traits

While the core library itself has 0 dependencies, you can enable the following package traits to integrate with additional dependencies:

  • StreamParsingSwiftCollections interops the library with types from Swift Collections.
  • StreamParsingFoundation interops the library with types from Foundation (enabled by default).
  • StreamParsingTagged interops the library with Tagged.
  • StreamParsingCoreGraphics interops the library with CoreGraphics types (enabled by default).

Documentation

The documentation for releases and main are available here.

Installation

You can add Swift Stream Parsing to an Xcode project by adding it to your project as a package.

https://github.com/mhayes853/swift-stream-parsing

[!NOTE] Xcode 26.4 is required for using traits directly in Xcode projects.

If you want to use Swift Stream Parsing in a SwiftPM project, it’s as simple as adding it to your Package.swift:

dependencies: [
  .package(
    url: "https://github.com/mhayes853/swift-stream-parsing",
    from: "0.5.0",
    // You can omit the traits if you don't need any of them.
    traits: ["StreamParsingSwiftCollections"]
  ),
]

License

This library is licensed under the MIT License. See LICENSE for details.

Package Metadata

Repository: mhayes853/swift-stream-parsing

Default branch: main

README: README.md