refactor: move serialization onto the shared traversal core - #111
Open
ehennestad wants to merge 2 commits into
Open
refactor: move serialization onto the shared traversal core#111ehennestad wants to merge 2 commits into
ehennestad wants to merge 2 commits into
Conversation
Contributor
Test Results (R2022a)759 tests +3 757 ✅ +3 2m 12s ⏱️ -18s For more details on these failures, see this check. Results for commit 6bf6259. ± Comparison against base commit d7b0925. ♻️ This comment has been updated with latest results. |
ehennestad
force-pushed
the
refactor-serializer-onto-transformer
branch
from
August 28, 2026 01:08
bd60069 to
9742b42
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## add-jsonld-deserializer #111 +/- ##
===========================================================
+ Coverage 79.28% 79.74% +0.45%
===========================================================
Files 422 422
Lines 4089 4048 -41
===========================================================
- Hits 3242 3228 -14
+ Misses 847 820 -27 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
BaseSerializer walked the instance graph itself, with its own visited registry, its own recursion accounting and its own mixed type unwrapping, none of it shared with the other two traversals in the codebase. BaseTransformer is the accumulating counterpart to BaseVisitor: each node is mapped to a representation and the representations of its children are composed into it. Its cycle semantics differ from the visitor on purpose. A visitor acts on each node once for the whole traversal; a transformer has to produce a value everywhere a node appears, so a node is marked only while its own subtree is being built. Meeting it again inside that subtree is a cycle and yields a reference; meeting it again elsewhere is not. BaseSerializer becomes a BaseTransformer subclass carrying only the openMINDS rules: an instance's own properties, a reference for every linked value, an inline representation without an identifier for every embedded value, and a queue of referenced instances to emit as documents of their own. SerializationContext is retired. Its visited registry comes from the traversal core, and its map of linked instances plus threaded recursion depth are replaced by that explicit queue, which also stops an instance referenced from several places being emitted more than once. AbstractSerializer is deleted. It was a two-method stub with no subclasses, superseded by BaseSerializer and now by BaseDeserializer. The output format is unchanged, which the golden fixture checks. That required reproducing one asymmetry: an embedded value that can occur only once is written as a single object, while everything else, including a lone linked value, is written as a list. Centralizing the shape rule made that asymmetry easy to lose, and the fixture caught it twice while this change was being written. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ehennestad
force-pushed
the
refactor-serializer-onto-transformer
branch
from
August 28, 2026 12:59
93b01aa to
6bf6259
Compare
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.
Completes the traversal rework from #69. The serializer walked the instance graph itself, with its own visited registry, its own recursion accounting and its own mixed type unwrapping, none of it shared with the resolver or the deserializer.
BaseTransformer
The accumulating counterpart to
BaseVisitor. Each node is mapped to a representation and the representations of its children are composed into it.The two protocols need different cycle semantics, which is why they are separate classes rather than one:
Edge hooks match
BaseVisitor, so both protocols read the same way.BaseSerializer
Now a
BaseTransformersubclass carrying only the openMINDS rules: an instance's own property values, a reference for every linked value, an inline representation with no identifier for every embedded value, and a queue of referenced instances to be emitted as documents of their own.SerializationContextis retired. Its visited registry comes from the traversal core, and itsLinkedInstancesmap plus the threaded recursion depth are replaced by that explicit queue. The queue also fixes a smaller thing: an instance referenced from several places is emitted once rather than once per reference.AbstractSerializeris deleted. It was a two-method stub with no subclasses, superseded byBaseSerializerand now byBaseDeserializer.The output format is unchanged
Byte-for-byte, which the golden fixture from the first PR in this stack verifies.
That took two attempts, and both failures are worth recording because they are the kind of thing a refactor loses silently. openMINDS documents written by this library have an asymmetric shape:
"age": {...}"species": [{"@id": "..."}]Centralizing the shape rule in one place made it natural to write "unwrap when the property is scalar", which changes every linked scalar in every document the library produces. The fixture caught it, then caught the mirror-image mistake when the rule was made uniformly "always a list". The asymmetry is now reproduced explicitly in
BaseSerializer.setPropertyValuewith a comment saying why.This is a good argument for the fixture tests existing at all: the round-trip suite passed in both broken states, because the library still agreed with itself.
A regression this introduced, and the coverage gap behind it
The first version of
doForLinkedEdgegathered the children of a linked property withobj.createReferences([children{:}]). Instances of different types cannot be concatenated, so a property holding more than one of its allowed types stopped serializing:This works on
main, where the container array is passed through whole andcreateReferencesreads.Instance.idfrom each element. Children are now referenced one at a time, which keeps them apart.The round-trip suite did not catch it, because
ommtest.helper.synthesizeInstancepopulates a property from a single allowed type and so only ever produces values of one type. A test is added for the multi-type case directly.Tests
Two cases added to
SerializationTest:🤖 Generated with Claude Code