Skip to content

Add SwiftMeasurementCodable: stable Measurement JSON via CLDR unit identifiers - #10

Merged
ken0nek merged 10 commits into
masterfrom
swift-measurement-codable
Jul 14, 2026
Merged

ken0nek merged 10 commits into
masterfrom
swift-measurement-codable

Conversation

@ken0nek

@ken0nek ken0nek commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a new standalone SwiftMeasurementCodable product that encodes and decodes Foundation Measurement values as stable, portable JSON — {"value": 21.5, "unit": "gram"} — where unit is a CLDR 48.2 core unit identifier (the same vocabulary JavaScript's Intl.NumberFormat accepts). Foundation's built-in Measurement Codable shape leaks display symbols and converter internals, so it is unusable as a wire format; because Measurement is already Codable and cannot be re-conformed, this uses small wrapper types.

The module imports only Foundation and has no dependency on the SwiftMeasurement target.

What's included

  • UnitIdentifierRepresentable — a public protocol on Dimension that maps units to and from CLDR identifiers via two switch statements (no stored tables, no shared state). All 22 Foundation Dimension types conform, and consumers can conform their own Dimension subclasses.
  • CodableMeasurement<UnitType> — a strict Codable/Equatable/Sendable wrapper that owns the {"value","unit"} wire shape and throws DecodingError on an unrecognized identifier, plus a Measurement.codable convenience.
  • RawMeasurement — a lenient passthrough that never fails on an unknown identifier, with failable typed access via measurement(as:).
  • 130 CLDR-mapped constants across all 22 dimension types (regular identifiers only).
  • A README section, and a CI job that builds and tests the module on macOS and Linux and verifies it stays standalone.

Testing

  • 270 tests, 0 failures on macOS (Swift 6.3); swift build -c release clean.
  • The added CI job builds (Debug + Release) and runs the suite on both macOS and Linux (Swift 6.0) on every push/PR, and checks the module does not import the SwiftMeasurement target.

Notes

  • Encoding emits the measurement's current unit as-is — convert first (measurement.converted(to:)) to control the wire unit.
  • A few identifiers (e.g. bar) are valid CLDR but fall outside the smaller ECMA-402 subset that Intl.NumberFormat formats.
  • CodableMeasurement's Equatable compares dimensionally (like Measurement), so 1 kg equals 1000 g even though they encode to different JSON.

ken0nek added 10 commits July 12, 2026 22:02
Constructing a Dimension subclass with the bare convenience initializer Unit(symbol:) fatal-errors on swift-corelibs-foundation (Foundation/Unit.swift:227 requires the designated init(symbol:converter:)). The unmapped-unit test cases now pass UnitConverterLinear(coefficient: 1); the converter is irrelevant since these units are only checked for a nil identifier. Verified green on Linux Swift 6.0 (269 tests) and macOS (269 tests).
The example claimed exact JSON bytes, but default JSONEncoder key order is not deterministic on Apple Foundation (Linux corelibs happened to match the old comment; macOS did not). Use .sortedKeys — as every test in the suite does — so the documented output is reproducible on all platforms. Corrected bytes verified against the built library.
The package-wide swift build/test already cover the new target on macOS and Linux; these add named, durable per-module checks — a Foundation-only isolation guard (the product must not depend on the SwiftMeasurement target), explicit debug+release product builds, and a filtered test run — so future edits to the shared steps can't silently drop Codable coverage.
The Foundation-allowlist grep had false positives (it blocked import Foundation with a trailing comment, import Foundation.NSString, and import Glibc) and false negatives (it missed @testable / @_implementationOnly import SwiftMeasurement). It now matches a real import of the SwiftMeasurement module directly, including attributed and scoped forms, while allowing system and Foundation-submodule imports. Also switches the standalone build from --product (which warns for an automatic library product and builds the whole package) to --target.
The README quick-start imported only SwiftMeasurementCodable but used Foundation types (Measurement, UnitMass, JSONEncoder), so it did not compile; it now imports Foundation. The protocol doc claimed symbol-based unit matching, but Foundation compares Dimension units by symbol AND converter, so a same-symbol/different-converter unit resolves to nil (verified). CodableMeasurement now documents that Equatable compares dimensionally (1 kg equals 1000 g despite different JSON) and that encode also throws for non-finite values.
Adds a test that resolves a unit through a further subclass (UnitDeepCrate: UnitCrateCount), exercising the as? Self guard that all 22 conformance files rely on; it must return nil, which it does. Previously untested because the only custom unit was final. Also factors the sortedKeys test encoder into a shared JSONEncoder.sortedKeysForTests helper, removing the duplicated inline copy from three test files.
@ken0nek
ken0nek merged commit 885d98b into master Jul 14, 2026
2 checks passed
@ken0nek
ken0nek deleted the swift-measurement-codable branch July 14, 2026 07:32
@ken0nek ken0nek self-assigned this Jul 14, 2026
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.

1 participant