Enum JSON compatibility
Use this reference to distinguish enum read acceptance, numeric writes and schema
admission. Go enum codecs are Not implemented. These captures are
source-distributed contract evidence included with v0.3.0, not Go runtime or
schema support. They do not add an enum package, registry, serializer or an enum
representation to concepts.Underlying.
Authority and scope
Section titled “Authority and scope”Authority is C# Fundamentals commit
d2accc4a79b6bcf2708213c97093ab5ba6c06381, with .NET/System.Text.Json 10.0.12:
- Bare converter
reads numeric tokens through
GetInt32and boxed-intEnum.IsDefined, reads strings through case-insensitiveEnum.TryParse, and writesConvert.ToInt32. - Bare factory admits all enum backing types; it does not validate Int32 compatibility.
- Concept converter
reads numbers with
GetInt32followed byEnum.Parse, strings with case-sensitiveEnum.Parse, and writes by casting the boxed enum toint. - Owning bare-enum specifications cover ordinary Int32 behavior. The boundary capture adds flags and alternate backing types; the unknown-string spec’s unquoted input does not isolate unknown member-name acceptance.
The fixture provenance records extraction, licensing, runtime identity and the zero-access stub witness. There are 236 actual observations: 224 serializer-level (167 reads, 57 independent writes), plus 12 direct converter diagnostics. Exact declarations, raw inputs and integer-string outputs are in the fixture set. Direct converter diagnostics are not wire acceptance: serializer null bypass and exception wrapping differ.
This source commit is not a published package pin. The separate packaged Chronicle profile below records Fundamentals 7.19.6; the source observations do not establish package equivalence.
The golden is historical, not a statement about current C# packages. A separate
.NET 10.0.12 source-profile comparison of
revision e8ac1ec
reproduced the original 236 records byte-for-byte, then exercised the same inputs
against the newer relevant converter closure. Only bare Bits numeric reads
5 and 7 changed from rejection to acceptance; 234 records were unchanged.
This comparison is not a published-package or kernel-admission claim, and the
committed golden below still describes the original pin.
The newer flags rule checks numeric inputs against the OR of declared bits. Strings and writes remain separate paths: an unknown bit can still parse from a numeric string and be written. A separate 42-observation wide-flags supplement observed numeric-read initialization overflow when a declared UInt32/Int64 flag exceeds Int32. Those observations do not establish support for every backing type, JIT profile or application schema.
The distinct non-Int32 enum-concept write failure remains tracked in Fundamentals #1150. Keep profiles and consumer admission decisions separate rather than silently replacing the historical golden.
Int32 acceptance matrix
Section titled “Int32 acceptance matrix”These are serializer-level observations with the original factories. Plain
declares 0, 1, 2, Int32 min/max, but not 3, 9 or -1. Bits declares None=0,
A=1, B=2, AB=3 and C=4 with [Flags]. NoZero declares only One=1.
Successful output is exact numeric JSON, not a member name.
| Input or operation | Bare enum | Enum concept |
|---|---|---|
Defined integer token 1, Int32 min/max | Accept and emit same integer | Same |
Undefined integer token 9, -1 | JsonException: not defined | Accept and emit same integer |
Exact name "One" | Accept 1, emit 1 | Same |
Wrong case "one", "ONE" | Accept 1 | ArgumentException |
Surrounding whitespace " One " | Accept 1 | Same |
Unknown name "Missing" | JsonException | ArgumentException |
Numeric strings "9", "+1", "01", " 9 " | Accept without membership check | Same |
Out-of-Int32 token 2147483648, -2147483649 | JsonException, inner FormatException | Same |
Out-of-Int32 string "2147483648" | JsonException | OverflowException |
Token 0 for NoZero | JsonException | Accept 0 |
String "0" for NoZero | Accept 0 | Same |
Token -0, string "-0" for Plain | Accept 0, emit 0 | Same |
| Empty or whitespace string | JsonException | ArgumentException |
JSON null | Non-nullable: JsonException; nullable: accept null, emit null | Accept null, emit null |
true, false | JsonException | InvalidCastException |
[], {} | JsonException | JsonException, inner InvalidOperationException |
Numeric 1.0, 1e0, 1E+0, 0.5 | JsonException, inner FormatException | Same |
Strings "1.0", "1e0" | JsonException | ArgumentException |
Flags declared combination token 3 | Accept 3 | Same |
Flags undeclared combinations 5, 7, unknown bit 8 | JsonException | Accept and emit same integer |
Flags strings "A, C", "5", "8" | Accept 5, 5, 8 | Same |
Flags string "a, c" | Accept 5 | ArgumentException |
Comma names without Flags, "One, Two" | Accept 3 | Same |
Duplicate name "One,One" | Accept 1 | Same |
"1, 2", "One,", unknown comma component | JsonException | ArgumentException |
| Write undefined Int32 value or undeclared flags combination | Emit exact integer, no membership check | Same |
The converter source selects membership and case behavior. Whitespace, signs, comma parsing and backing-range checks also depend on BCL Enum parsing at the runtime pin. CLR exception messages are diagnostics, not cross-language error text.
Backing-type failures
Section titled “Backing-type failures”The factory’s acceptance of a CLR enum type is not evidence that its values can be read and written safely. These failures were executed for byte, uint and long; other non-Int32 backing types were not captured.
| Operation | Byte | UInt32 | Int64 |
|---|---|---|---|
| Bare numeric token within Int32, even a declared value | ArgumentException: boxed Int32 differs from backing type | Same | Same |
| Bare string within backing range | Case-insensitive parse; can write byte range | Full UInt32 parse range; write above Int32 max overflows | Full Int64 parse range; write outside Int32 overflows |
| Concept numeric token within Int32 | Accept 0–255; -1/256 overflow | Accept nonnegative Int32; -1 overflows | Accept all tested Int32 values |
| Concept string within backing range | Case-sensitive parse | Same | Same |
| Non-null concept write, including zero | InvalidCastException | Same | Same |
| Numeric token outside Int32 | GetInt32 fails before backing conversion | Same | Same |
Concept write failure is boxed-enum-to-Int32 unboxing, not just range overflow.
For example, reading "9223372036854775807" as a long concept succeeds, preserves
that precise integer, then writing fails. A string name parsing successfully does
not make an alternate backing type interoperable.
No universal round trip
Section titled “No universal round trip”At the historical d2accc4 source pin, bare reads and writes are asymmetric.
Reading "9" succeeds and emits 9, but reading that numeric output fails
membership. Writing flags value 5 emits 5, which bare read rejects unless 5 is
an exact declared member. That pin has no automatic allowed-bit-mask rule;
the newer e8ac1ec source behavior differs as described above. Test read acceptance
and write output independently, not as a universal round-trip requirement.
JsonSerializer accepts a null concept without calling its converter; a direct concept converter call with null throws ArgumentNullException. Nullable bare null is likewise a serializer-wrapper observation. Missing properties, null omission, defaults and collection elements depend on a containing product profile and are outside this scalar capture.
Packaged Chronicle profile
Section titled “Packaged Chronicle profile”The public owner checkpoint
accepts the separately captured profile at Chronicle.Go
747c1eb3a015a583b4fd2ffa51b6c6ab3a185087:
profile.json, provenance.json,
capture harness and capture scope. As recorded,
it executes packaged Chronicle 19.29.4 / Fundamentals 7.19.6 on
.NET/System.Text.Json 10.0.12, through actual EventSerializer and both
JsonSchemaGenerator.Generate / GenerateForReadModel APIs. These are reviewed
owner-captured observations, not a package execution repeated by Fundamentals.Go.
Each naming policy records 586 reads, 130 independent writes, 208 Expando
direction-pairs and 54 schemas; none is a Go codec or kernel-admission test.
The six historical enum declarations match. Comparing root scalar inputs with
Scalar<T> property cases maps 73 bare reads and 28 independent writes per
policy, with agreement after normalizing acceptance, exception/inner categories,
exact integers and numeric property output. This excludes messages, whole-object
bytes, concepts, direct diagnostics and standalone nullable cases; it is not
a 236-case consumer pass or general source/package equivalence. Package AllBits
declares None/A/B/C/AB/All; the newer source supplement declares only None/A/All.
Do not transfer conclusions between those declarations by type name.
Options also differ from the two-factory historical harness. Client/event options
include EnumConverterFactory; schema options install only the enumerable-concept
and concept factories. Event property matching is case-insensitive; client/schema
matching is not. Client/event writes omit nulls; schema options record Never.
DefaultNamingPolicy preserves property names; CamelCaseNamingPolicy uses
AcronymFriendlyJsonCamelCaseNamingPolicy. Neither renames enum members.
The profile records complete converter order and settings separately.
Locate the following cases in profile.json by naming policy, declared type,
operation, schema API when present, and ID—not by array index. Outcomes below
hold for both naming policies; Expando cases use both schema APIs.
| Operation and case identity | Recorded outcome |
|---|---|
EventSerializer.Deserialize, Scalar<Bits>, token:5 / token:7 | Reject numeric inputs, unlike newer e8ac1ec source reads. |
Same type/operation, token:"A, C", token:"a, c", token:"5", token:"8" | Accept 5, 5, 5, 8; separately recorded reserialization writes those integers. |
EventSerializer.Serialize, Scalar<Bits>, numeric:5 / numeric:7 / numeric:8 | Independent writes emit 5, 7, 8; numeric read acceptance does not follow. |
ExpandoObjectConverter.ToExpandoObject/ToJsonObject, Scalar<Bits>, token:5 | JSON-to-CLR preserves 5; subsequent toJson emits "None". |
Same operation, NullableScalar<Int32Sample>, token:9 | JSON-to-CLR preserves 9; toJson omits the property. |
Same operation, ArrayValue<Bits>, token:[3,5,7,8,-1] | JSON-to-CLR preserves the integers; toJson emits ["AB",null,null,null,null]. |
For ConceptDefaultControl, Generate records only {"default":null} for
Value (camel policy: value). GenerateForReadModel records integer type,
enum, aligned x-enumNames and default:null, with no nullable type union or
null enum member. That shape proves neither null admission nor general Go enum
support. Expando conversion is in-memory schema conversion, not Mongo/BSON or
kernel persistence: scalar fallback, nullable omission and array null elements
are distinct, not a rule that every unknown value becomes null.
Consumer ownership and remaining evidence
Section titled “Consumer ownership and remaining evidence”Arc.Go and Chronicle.Go own enum declarations, immutable member tables/generated
codecs, JSON field plans and schema admission. Preserve original C# member names
separately from camelCased TypeScript exports: a generated TS name is not the
concept converter’s accepted name. Go reflection cannot discover named-type
constants, and String() output is not a declaration or parse-name source.
Each product must identify bare versus concept mode, backing type, exact values, flags metadata, accepted names and null/presence rules. Numeric parse acceptance, write range and schema membership are separate decisions. TypeScript’s safe-number range is not the C# converter’s Int32 write range; schema admission may deliberately be narrower than serializer parsing, but must be evidenced explicitly. Chronicle also owns persisted schema generations and protection admission.
The concepts.Concept[T] and Underlying scalar allowlist remain unchanged.
An existing Int32 scalar concept with product-owned validation does not establish
general enum-concept recognition. There is no shared enum registry or serializer API.
#17 delivered the historical and packaged-profile evidence; consumer adoption is tracked in Chronicle.Go#64 and Arc.Go#32. The reviewed Chronicle package capture supplies bounded event/schema evidence, not an admitted Go enum profile. Arc binding/proxy, backend/frontend agreement and Chronicle kernel/Mongo/BSON admission remain product-owned and unverified here. Neither fixture validation nor these .NET captures prove Go codec parity. See the parity map for implementation status.