Skip to content

Say what a value asked for as a base type or an interface is - #2483

Merged
danielcweber merged 1 commit into
14.xfrom
fix/declared-type-for-base-types-contract
Oct 1, 2026
Merged

danielcweber merged 1 commit into
14.xfrom
fix/declared-type-for-base-types-contract

Conversation

@danielcweber

@danielcweber danielcweber commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

The contract both GraphSON deserializers are tested against now says what a value is when it is asked for as a base type or an interface - an IComparable, a ValueType, an EnumWrapper, the non-generic IEnumerable or IDictionary - where it said nothing before and the two implementations disagreed. A typed value is the type its name stands for to everything that can hold that type: a g:Int32 is an int to an IComparable, a g:T a T to an EnumWrapper. A bare scalar is what it is when asked for as an object. An array is an object[] and an object a Dictionary<string, object> to the non-generic collection interfaces.

Changes

  • Tests.Infrastructure: 51 tests in GraphsonSupportTestBase, after DateTime_from_typed_Timestamp, and VerifyAttemptWithRuntimeType, which records the type of an answer next to its value - a snapshot of 5 does not say whether it is an int or a long.
    • A typed value as the type its name stands for: g:Int32, g:Float, g:Double, g:UUID, g:Date, g:Timestamp and the gx: numbers, gx:Duration and gx:Char asked for as an IComparable, IConvertible, IFormattable, ValueType or IEquatable<> of themselves; g:T, g:Direction and g:Merge as an EnumWrapper; g:Tree as an IEnumerable.
    • Where the requested type cannot hold that type, or the @value does not fit it, the bare @value is read instead - a g:UUID is a string to an IConvertible, a g:Int32 "abc" a string to an IComparable. Nothing throws.
    • A bare string, number and bool as a string, a long or double and a bool to whatever can hold one; a bare 5 is no IEquatable<int>. The items of an IComparable[] likewise.
    • An array, a g:List and a bulk set as an object[] to IEnumerable, ICollection and IList; an object and a map keyed by strings as a Dictionary<string, object> to IDictionary, ICollection and IEnumerable.
  • Support.NewtonsoftJson.Tests: the 51 snapshots.

Notes

  • Tests only. Every snapshot is what the Newtonsoft implementation answered before this change; nothing in src changes.
  • Measured on the System.Text.Json implementation, which verifies against the same snapshots: 47 of the 51 differed there. Gremlinq/Gremlinq.Extensions#218 brings it to all 51 and has to be merged after this.
  • Left unpinned on purpose, because Newtonsoft's answer there looks accidental: a bare true or "abc" as an IFormattable, a bare number or bool as an ICloneable or IEnumerable, and an object as an IList or ICloneable come back as the JValue or JObject itself; a map keyed by ints as an IDictionary comes back as an empty Dictionary<string, object>; an element map as an IDictionary comes back rebuilt as { id, label, properties }.
  • Newtonsoft: 299 tests before, 350 after, all passing.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.70%. Comparing base (34c5793) to head (48327a6).

Additional details and impacted files
@@            Coverage Diff             @@
##             14.x    #2483      +/-   ##
==========================================
+ Coverage   93.68%   93.70%   +0.01%     
==========================================
  Files         279      279              
  Lines        7903     7905       +2     
  Branches      885      886       +1     
==========================================
+ Hits         7404     7407       +3     
  Misses        310      310              
+ Partials      189      188       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@danielcweber
danielcweber force-pushed the fix/declared-type-for-base-types-contract branch from e5a3b0b to b5383f3 Compare October 1, 2026 02:34
… it stands for

The shared GraphSON tests asked for a value as the type it is, or as an object. What
a g:Int32 is to an IComparable, a bare 5 to a ValueType or an array to the
non-generic IEnumerable was pinned nowhere, and the two implementations disagreed
about most of it. 51 tests now say what this implementation answers. As a snapshot
of 5 does not say whether it is an int or a long, they record the type next to the
value, through VerifyAttemptWithRuntimeType.

- A typed value is the type its name stands for to everything that could hold that
  type: a g:Int32 is an int to an IComparable, a ValueType and an IEquatable<int>,
  a g:Float a float, a g:UUID a Guid, a g:Date a DateTimeOffset, a g:T a T to
  Gremlin.Net's EnumWrapper, a g:Tree a Tree<object> to the non-generic IEnumerable.
- Where the requested type cannot hold that type, or the @value does not fit it,
  the bare @value is read instead: a g:UUID is a string to an IConvertible, and a
  g:Int32 "abc" a string to an IComparable. Nothing throws. Where the bare @value
  cannot be held either, there is no answer.
- A bare scalar is to everything that could hold it what it is to an object: a
  string, a long or a double, a bool. A bare 5 is a long, so it is no
  IEquatable<int>.
- The items of an array are read the same way.
- An array, a g:List and a bulk set are an object[] to the non-generic IEnumerable,
  ICollection and IList.
- An object, and a map whose keys are all strings, are a Dictionary<string, object>
  to the non-generic IDictionary, ICollection and IEnumerable.

No implementation changes: all 51 are what this implementation answered before.
Measured on the System.Text.Json implementation in Gremlinq.Extensions, which runs
the same tests against the same snapshots: 47 of the 51 differ there today. It
declines where a typed value or a bare scalar is asked for as an interface, hands
out its own JsonElement for a ValueType and its own JsonMap for an object asked for
as an IEnumerable, and builds a List<object> and a Dictionary<object, object> where
an object[] and a Dictionary<string, object> are built here.

Left out on purpose, because this implementation's answer is its own token rather
than a value: a bare true or "abc" asked for as an IFormattable, any bare scalar but
a string as an ICloneable or an IEnumerable, and an object as an IList or an
ICloneable all come back as the JValue or JObject itself. A map keyed by ints, asked
for as an IDictionary, comes back as an empty Dictionary<string, object>.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@danielcweber
danielcweber force-pushed the fix/declared-type-for-base-types-contract branch from b5383f3 to 48327a6 Compare October 1, 2026 03:21
@danielcweber
danielcweber merged commit 48327a6 into 14.x Oct 1, 2026
5 checks passed
@danielcweber
danielcweber deleted the fix/declared-type-for-base-types-contract branch October 1, 2026 14:18
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