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:
StreamParsingSwiftCollectionsinterops the library with types from Swift Collections.StreamParsingFoundationinterops the library with types from Foundation (enabled by default).StreamParsingTaggedinterops the library withTagged.StreamParsingCoreGraphicsinterops 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.
[!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