glideapps / glideapps/quicktype
[BUG]: Swift 6 Strict Sendable conformance
- Dominant language
- TypeScript
- Stars
- 13.9k
- Forks
- 1.2k
- Avg merge
- 8h 53m
- Merged PRs (30d)
- 369
Description
## Issue Type
TLDR; Quicktype is generating invalid Swift under the Swift 6 language compiler
Generated Swift helper classes (`JSONCodingKey`, `JSONAny`, `JSONNull`) don't compile under Swift 6 strict concurrency, even when `--sendable` is passed.
## Context (Environment, Version, Language)
Input Format: JSON Schema (`--src-lang schema`)
Output Language: Swift
CLI, npm, or app.quicktype.io: CLI
Version: `23.2.6`
## Description
When generating Swift code with `--sendable` and `--struct-or-class struct`, the generated model structs correctly gain `Sendable` conformance. However, the helper classes at the bottom of the output (`JSONNull`, `JSONCodingKey`, `JSONAny`) are not updated for Swift 6 strict concurrency:
1. `JSONCodingKey` is a non-final class — Swift 6 errors with `non-final class 'JSONCodingKey' cannot conform to 'Sendable'`
2. `JSONAny` stores `Any` which is not `Sendable` — needs `@unchecked Sendable` and `nonisolated(unsafe)` (related: https://github.com/glideapps/quicktype/issues/2598)
3. `JSONNull` uses deprecated `hashValue` (minor, but warning noise)
This makes the output unusable with `swift-tools-version: 6.0` packages out of the box.
## Input Data
Any JSON Schema that produces `additionalProperties` / untyped fields triggers `JSONAny` usage. Minimal example:
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Example",
"type": "object",
"properties": {
"data": {
"type": "object",
"additionalProperties": true
}
}
}
```
## Expected Behaviour / Output
When `--sendable` is passed, all generated types — including `JSONCodingKey`, `JSONNull`, and `JSONAny` — should compile cleanly under Swift 6 strict concurrency (`swift-tools-version: 6.0`).
Specifically:
- `JSONCodingKey` should be `final class JSONCodingKey: CodingKey, Sendable`
- `JSONAny` should be marked `@unchecked Sendable` (since it wraps `Any`)
- `JSONNull` should use `hash(into:)` instead of deprecated `hashValue`
## Current Behaviour / Output
```
error: non-final class 'JSONCodingKey' cannot conform to 'Sendable'; use '@unchecked Sendable'
warning: 'Hashable.hashValue' is deprecated as a protocol requirement; conform type 'JSONNull' to 'Hashable' by implementing 'hash(into:)' instead
```
The generated struct models are correctly `Sendable`, but the helper classes break the build.
## Steps to Reproduce
1. `npm install -g quicktype`
2. Save the minimal JSON Schema above as `example.json`
3. Run:
```bash
quicktype \
--lang swift \
--src-lang schema \
--src example.json \
--access-level public \
--struct-or-class struct \
--coding-keys \
--sendable \
-o Models.swift
```
4. Create a Swift package with `swift-tools-version: 6.0` and try to build
```bash
mkdir MyPackage && cd MyPackage
swift package init --type library --name MyPackage
sed -i '' 's/swift-tools-version: .*/swift-tools-version: 6.0/' Package.swift
cp ../Models.swift Sources/MyPackage/Models.swift
swift build
```
## Possible Solution
I'm currently working around this by making the following changes with `sed` after generation:
1. Emit `final class JSONCodingKey: CodingKey, Sendable` instead of `class JSONCodingKey: CodingKey`
2. Emit `public class JSONAny: Codable, @unchecked Sendable` with `nonisolated(unsafe)` on the `value` property
3. Replace `public var hashValue: Int` with `public func hash(into hasher: inout Hasher) {}` on `JSONNull`
For point 2 this is would be safer as an enum for future extensibility
```swift
public enum JSONValue: Codable, Sendable {
case null
case bool(Bool)
case int(Int64)
case double(Double)
case string(String)
case array([JSONValue])
case object([String: JSONValue])
}
```
Contributor guide
Assessment
This issue has not been assessed yet.