Repository navigation
feat(codecs): add empty-or-null nested-object codec and apply it to congressional net worth - #405
Merged
Merged
Conversation
This pull request adds a new codec for required wire keys that hold a nested object whose absent value the provider sends as an empty string or JSON null, together with its gen_go support (ADR 0033, #400). **Runtime codec:** * Added `empty_or_null_object` to `crates/libfmp/src/codecs.rs`, which decodes `""` and null to `None`, a JSON object to `Some(T)`, and rejects any other value with an error that never echoes the value. The key stays required and serialization uses serde's default. **Go generation:** * Taught `gen_go` to recognize the `empty_or_null_object::deserialize` path on an `Option` of a response struct, mapping it to a `jsontext.Value` shadow and emitting the raw-shadow decode that unmarshals the inner object when the value is neither null nor `""`. * Rejected misuse with a clear error when the codec is applied to anything other than an `Option` of a known response struct. **Tests:** * Added runtime tests covering empty, null, object, and invalid inputs (including that errors never leak the value), and a gen_go test verifying the rendered shadow, decode, and misuse diagnostics.
The `incomeRange` member on `CongressionalMemberNetWorth` used `required_option`, which rejected the empty-string value that live `senate-net-worth` rows send. A probe of 4 members (993 rows) saw `""` on `incomeRange` 47 times, so decoding failed on real data. Switch the member to the new `codecs::empty_or_null_object` codec, which maps `""` and null to `None`, decodes a JSON object into the nested struct, and errors on any other shape without leaking the value. The Go SDK gains the matching guard so `""` decodes to `nil`. The member was already `Option`, so the public type is unchanged and this is not breaking. Drop the now-unused `allow(dead_code)` on the codec module, document the case in ADR 0033, and add Rust, Python, and Go parity tests covering the empty, null, object, and rejected-value paths. close #400
Extend the ADR 0033 codec notes in the Go SDK architecture ADR to cover `crate::codecs::null_text::deserialize` (treats the text `"NULL"` and null as nil, #380) and `crate::codecs::empty_or_null_object::deserialize` on an `Option` of a response struct (`""` and null are nil, objects decode into the struct with a `*T` public field, #400).
benbenbang
enabled auto-merge (rebase)
October 2, 2026 03:34
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Live
senate-net-worthrows send an empty string for theincomeRangenested object, which the existingrequired_optioncodec rejected and caused decoding to fail on real data (a probe of 4 members over 993 rows saw""47 times). This adds a dedicated codec for required wire keys whose nested object may arrive as an empty string or JSON null, and wires it through the runtime, Go generation, and the congressional response.Runtime codec:
empty_or_null_objectto libfmp, decoding""and null toNone, a JSON object toSome(T), and rejecting any other value with an error that never echoes the value.Go generation:
empty_or_null_object::deserializepath on anOptionof a response struct and emit ajsontext.Valueshadow with raw-shadow decode that unmarshals the inner object unless the value is null or"".Optionof a known response struct.Congressional fix:
CongressionalMemberNetWorth.incomeRangefromrequired_optionto the new codec so""and null decode toNone; the member was alreadyOption, so the public type is unchanged.Docs and tests:
allow(dead_code)on the codec module and document the new codec (andnull_text) cases in ADRs 0030 and 0033.close #400