Skip to content

TOON 3.0 Specification Compliance#3

Merged
mattt merged 8 commits into
toon-format:mainfrom
alexey1312:feature/update-for-spec
Dec 1, 2025
Merged

TOON 3.0 Specification Compliance#3
mattt merged 8 commits into
toon-format:mainfrom
alexey1312:feature/update-for-spec

Conversation

@alexey1312
Copy link
Copy Markdown
Contributor

@alexey1312 alexey1312 commented Nov 24, 2025

This PR updates TOONEncoder to fully comply with the TOON v3.0 specification.

Changes

Updated: Specification Version

  • TOONEncoder.specVersion now returns "3.0"

Added: flattenDepth Property (TOON 3.0)

  • Controls maximum segments in a folded path
  • Default: Int.max (unlimited)
  • Values < 2 have no practical effect
  • Example: flattenDepth = 2 folds {a: {b: {c: 1}}}a.b: then nested c: 1

Added: Collision Avoidance (TOON 3.0)

  • Key folding now checks for sibling key collisions
  • Folding is skipped if the resulting path would collide with an existing literal key
  • Per spec §13.4: "The resulting folded key string MUST NOT equal any existing sibling literal key"

Fixed: Nested Folding After Depth Limit

  • When flattenDepth is reached, remaining nested structures are encoded without further folding
  • Per spec §13.4: "emit the remaining structure as normal nested objects"

Previously Implemented (TOON 2.1)

  • ✅ Canonical number formatting (no trailing zeros, -0 → 0)
  • ✅ Key folding with .safe mode
  • ✅ Proper escape sequences
  • ✅ Three delimiter types

Test Coverage

  • Added 4 tests for flattenDepth behavior
  • Added 2 tests for collision avoidance
  • All 94 tests passing

@alexey1312 alexey1312 changed the title TOON 2.1 Specification Compliance TOON 3.0 Specification Compliance Nov 26, 2025
@alexey1312
Copy link
Copy Markdown
Contributor Author

@mattt Friendly ping on this one 🙂

@mattt
Copy link
Copy Markdown
Collaborator

mattt commented Dec 1, 2025

@alexey1312 Thanks for the ping. Taking a look now...

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the TOONEncoder to comply with the TOON 3.0 specification, building upon the existing 2.1 implementation. The changes introduce two major new features from TOON 3.0 (flattenDepth control and collision avoidance) while maintaining backward compatibility with all existing 2.1 features.

Key Changes:

  • Added flattenDepth property to limit the depth of key folding (default: unlimited)
  • Implemented collision avoidance to prevent folded keys from conflicting with sibling literal keys
  • Updated specVersion to "3.0" to reflect compliance with the latest specification
  • Enhanced canonical number formatting with explicit minimumFractionDigits = 0 configuration

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
Sources/TOONEncoder/TOONEncoder.swift Added specVersion constant, flattenDepth property, expandPaths property (unused), updated key folding logic to support depth limits and collision detection, enhanced number formatter for canonical formatting
Tests/TOONEncoderTests/TOONEncoderTests.swift Added comprehensive test coverage for key folding (6 tests), flattenDepth behavior (4 tests), and collision avoidance (2 tests); added version declaration test
README.md Updated documentation to reflect TOON 3.0 compliance, added sections for key folding and flatten depth with examples, updated spec URL to point to toon-format/spec repository

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Sources/TOONEncoder/TOONEncoder.swift
Comment thread Sources/TOONEncoder/TOONEncoder.swift
Comment thread Tests/TOONEncoderTests/TOONEncoderTests.swift Outdated
Comment thread Tests/TOONEncoderTests/TOONEncoderTests.swift Outdated
Comment thread Sources/TOONEncoder/TOONEncoder.swift
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

Sources/TOONEncoder/TOONEncoder.swift:316

  • Lines 281-292 and 309-316 contain redundant if-else branches. In both cases, whether keyOrder.isEmpty or not, the same content ("\(encodedKey):") is written. The only difference is whether encodeObject is called afterward. Consider simplifying to unconditionally write the key, then conditionally call encodeObject only when keyOrder is not empty:
write(depth: depth, content: "\(encodedKey):", to: &output)
if !keyOrder.isEmpty {
    encodeObject(values, keyOrder: keyOrder, output: &output, depth: depth + 1, allowFolding: !hitDepthLimit)
}
            case .object(let values, let keyOrder):
                if keyOrder.isEmpty {
                    write(depth: depth, content: "\(encodedKey):", to: &output)
                } else {
                    write(depth: depth, content: "\(encodedKey):", to: &output)
                    encodeObject(
                        values,
                        keyOrder: keyOrder,
                        output: &output,
                        depth: depth + 1,
                        allowFolding: !hitDepthLimit
                    )
                }
            }
            return
        }

        // Regular encoding without folding
        let encodedKey = encodeKey(key)

        switch value {
        case .null, .bool, .int, .double, .string, .date, .url, .data:
            if let encodedValue = encodePrimitive(value, delimiter: delimiter.rawValue, inObject: true) {
                write(depth: depth, content: "\(encodedKey): \(encodedValue)", to: &output)
            }

        case .array(let array):
            encodeArray(key: key, array: array, output: &output, depth: depth)

        case .object(let values, let keyOrder):
            if keyOrder.isEmpty {
                write(depth: depth, content: "\(encodedKey):", to: &output)
            } else {
                write(depth: depth, content: "\(encodedKey):", to: &output)
                encodeObject(values, keyOrder: keyOrder, output: &output, depth: depth + 1)
            }
        }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread Sources/TOONEncoder/TOONEncoder.swift
Comment thread Sources/TOONEncoder/TOONEncoder.swift
@mattt
Copy link
Copy Markdown
Collaborator

mattt commented Dec 1, 2025

@alexey1312 Thanks so much for your contribution. This is looking good! I just made a few changes, most notably removing the unused KeyFolding type / expandPaths property, which would only apply for decoding. I don't see a particular benefit to implementing TOONDecoder beyond completeness, but if that does happen, we can add those back then.

@mattt mattt merged commit 0c8c491 into toon-format:main Dec 1, 2025
3 checks passed
@mattt
Copy link
Copy Markdown
Collaborator

mattt commented Dec 1, 2025

This is now available in version 0.2.0.

@alexey1312 alexey1312 deleted the feature/update-for-spec branch December 1, 2025 09:40
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.

3 participants