fix: give each object property one key and refuse two that own the same (#169) - #181
Merged
Merged
Conversation
…me (#169) MapEncoders.object took EntryEncoder, which could write any number of keys into the shared map, so object could not see which keys its parts own and a repeated key silently replaced the earlier value. The Raoh Specification's object encoder is a list of properties of one member each. PropertyEncoder now owns exactly one key and writes it at most once: the encoded value, null, or nothing. optionalProperty and presenceProperty return PropertyEncoder, built inside PropertyEncoder with a private omit marker, so the only write to the map is PropertyEncoder.encodeTo putting its own key. EntryEncoder and PropertyEncoder.encode(T) are removed; key() and encodeTo are package-private. object copies the properties and checks that copy for a repeated key when it is built, throwing IllegalArgumentException with the key and both positions, whatever the property kinds. With at most one key per property, the output map is now sized to the property count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Closes #169.
Cause
MapEncoders.objecttookEntryEncoder, a function that could write any number of keys into the shared output map. The key a property writes lived only inside its closure, soobjectsaw nothing but a sequence of writes, and a repeated key showed up only as a secondLinkedHashMap.put: the later value replaced the earlier one in the earlier one's position. The Raoh Specification'sobjectencoder is a list of properties of one member each, withrequires: distinct_members; the Java type was wider than that.Adding
key()toEntryEncoderwould have left two sources of truth (the declared key and the keys actually written), so this removes the generalisation instead.Change
PropertyEncoderowns exactly one key and writes it at most once: the encoded value,null, or nothing. It holds the key and a function that returns the value or a private omit marker;encodeTois the only place that writes to the map, and it writes only its own key. The constructor stays private, so no property can write a different key.optionalPropertyandpresencePropertyreturnPropertyEncoder<T>, built byPropertyEncoder.ofOptional/ofPresencelike the other factories.EntryEncoderis removed.PropertyEncoder.encode(T)is removed (it cannot say "leave the key out");key()andencodeToare package-private.object(PropertyEncoder<T>...)copies the properties (as since fix: keep oneOf's candidate issues as issues, and say "elements" for map sizes (#167) #178) and checks that copy when it is built, throwingIllegalArgumentException("object key 'a' is owned by properties 0 and 2"). The check does not look at values, sooptionalProperty("a")withproperty("a")is refused too.LinkedHashMapresized from the 13th key.discriminate's rule that the injected tag wins over a body's entry is unchanged; it is a composition rule, not a duplicate definition.The only other
requiresin the specification catalogue on the encoder side is this one; the decoder-side ones were handled earlier.Tests
MapEncoderTest: same key twice (message with key and positions), across property kinds with a getter that must not run, declaration order around an omitted key,object()with no properties.nullablePropertyWritesNullWhenGetterReturnsNullnow checks the key is present, not just thatgetis null.GivenCollectionsAreCopiedTest: changing the array afterwards, even into a duplicate, changes nothing.nullinstead of omitting (optional,Absent), omitting forPresentNull, and dropping the copy each fail tests. Replacing the pre-size with the default constructor passes, deliberately: sizing is not behaviour.mvn verify(all modules, effect audit),-Pnullcheck clean compileon Zulu 25 and javadoc pass.Not in this PR
The specification suite has no format for a definition rejected when it is built, so
distinct_membersis covered by raoh-java's tests only. That belongs in a separate raoh-specification issue.🤖 Generated with Claude Code