Skip to content

fix(json-cppagent): drop JsonConditionsConverter, derive list shape - #221

Merged
PatrickRitchie merged 5 commits into
TrakHound:masterfrom
ottobolyos:fix/jsonconditions-structural-rewrite
Aug 26, 2026
Merged

PatrickRitchie merged 5 commits into
TrakHound:masterfrom
ottobolyos:fix/jsonconditions-structural-rewrite

Conversation

@ottobolyos

@ottobolyos ottobolyos commented Aug 17, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Closes the ~200-line hand-written JsonConditionsConverter and rewrites JsonConditions as List<JsonConditionWrapper>-derived so the type shape IS the cppagent JSON v2 wire shape. System.Text.Json's default serializer handles both directions with no custom converter — round-trip is symmetric by construction, no reader-state edge cases to defend against, and the wire is byte-compatible with the pre-refactor output when the emission pipeline uses the same sparse-serialization options.

Filed against dime-connector bug report #15 (jsonconditions-replace-converter-overlay-with-structural-rewrite-at-7-0.md); this is the mtc.net upstream side of that follow-up to #12 (v1 → v2 wire-shape fix). Targeted at the 7.0 major-bump window so the API-break window is already open.

Change

  • New JsonConditionWrapper (libraries/MTConnect.NET-JSON-cppagent/Streams/JsonConditionWrapper.cs) — four nullable level properties (Fault/Warning/Normal/Unavailable) with [JsonIgnore(WhenWritingNull)] for the single-key envelope; Value / Level convenience read-side accessors; ToObservation() materialiser; and Of{Fault,Warning,Normal,Unavailable} static factories.
  • JsonConditions rewritten to inherit List<JsonConditionWrapper> with three ctors: empty, copy-from-wrappers, and the legacy IEnumerable<IObservationOutput> overload — the last preserves the historical FAULT → WARNING → NORMAL → UNAVAILABLE level-order emission so JsonComponentStream's call site is a drop-in replacement.
  • JsonConditionsConverter deleted — the entire class.

Behavioral changes (intentional at 7.0)

  • Legacy MTConnect JSON v1 object-keyed READ compat is dropped. The default List<T> deserializer only reads arrays; the pre-7.0 fallback to {"Fault":[…], "Warning":[…]} envelope is removed. #12 already flipped the write path to v2; this closes the same transition on the read path.
  • Mixed-level wire interleaving is no longer re-bucketed by level on write. Insertion order is preserved end-to-end. Producers that want the historical level-order can either use the IEnumerable<IObservationOutput> ctor (unchanged) or sort explicitly before construction.
  • Strict malformed-wrapper rejection with named JsonException messages is dropped. Default S.T.J tolerance applies; malformed input produces default S.T.J errors instead of custom prose.

Dime review cycle 1

Retroactive backfill (2026-08-20). The 6-agent Ultrareview cycle ran on this PR and its findings landed atomically in commit aa9c84c0 (fix(json-cppagent): close Ultrareview findings on JsonConditions surface) with supporting failure-path pins in 00c6ab23 (test(json-cppagent): pin dropped-converter failure paths + level accessor precedence). Ledger reconstruction from commit history:

  • [TEST] test-coverage-audit — dropped-converter failure paths (malformed wrapper, mixed-level ordering) + Level accessor precedence on multi-property wrappers required behavior pins; landed in 00c6ab2.
  • [FINDING] code-review — JsonConditionWrapper.Level/Value read-side accessor precedence needed explicit tie-break behaviour when multiple level properties are populated; closed in aa9c84c.
  • [DOCS] documentation-audit — XML /// docs on the new public surface (JsonConditionWrapper, its factories, JsonConditions ctors) required tightening for the 7.0 API break; closed in aa9c84c.
  • [SIMPLIFY] simplification — verified the structural rewrite is at minimum shape: 4 nullable level properties + [JsonIgnore(WhenWritingNull)] gives the single-key envelope with zero custom converter code.
  • [FINDING:A08] security-audit — deserialization surface reviewed; default S.T.J tolerance is intentional at the 7.0 break window per PR body's "Behavioral changes" section.
  • [IMPROVE] improvement — no impact-ranked follow-ups surfaced beyond the accessor-precedence + docs items already fixed in aa9c84c.

(Zero unfixed findings — Ready-eligible.)

Depends on

@ottobolyos ottobolyos changed the title fix(json-cppagent): drop JsonConditionsConverter; list-derived shape fix(json-cppagent): drop JsonConditionsConverter, derive list shape Aug 17, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 19, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 19, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 19, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 19, 2026
- V2_7DataItemTypeTests.cs deleted (accepts TrakHound#233 phase-1 consolidation)
- JsonConditionsArrayShapeTests.cs union-resolved: kept TrakHound#221 structural rewrite,
  reapplied 4 (Action) casts on Assert.Throws<JsonException> sites for NUnit 4
  compatibility.

Pre-existing DeviceValidationLevel duplicate on AgentConfiguration.cs (line 158)
was already present on origin/integration/up-to-pr-224 (verified via clean tail
build) — not introduced by this PR.
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 19, 2026
Rebuilt after Ultrareview cycle 1 MEDIUM fix on chore/nunit-4-upgrade
(head 5e205e3): 3 ClassicAssert.AreEqual sites migrated to
Assert.That constraint form, both NUnit.Framework.Legacy imports dropped.

- V2_7DataItemTypeTests.cs deleted (accepts TrakHound#233 phase-1 consolidation)
- JsonConditionsArrayShapeTests.cs union-resolved via rerere: kept TrakHound#221
  structural rewrite, reapplied 4 (Action) casts on Assert.Throws sites
  for NUnit 4 compatibility.

Pre-existing DeviceValidationLevel duplicate on AgentConfiguration.cs (line
158) already present on origin/integration/up-to-pr-224 (verified via clean
tail build) — not introduced by this PR.
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 19, 2026
@ottobolyos
ottobolyos force-pushed the fix/jsonconditions-structural-rewrite branch 3 times, most recently from bcb481b to 0a889c0 Compare August 21, 2026 14:07
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
@ottobolyos
ottobolyos force-pushed the fix/jsonconditions-structural-rewrite branch from 0a889c0 to 5f7d520 Compare August 21, 2026 16:05
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 21, 2026
@ottobolyos
ottobolyos force-pushed the fix/jsonconditions-structural-rewrite branch 2 times, most recently from ff0c879 to 998862c Compare August 22, 2026 08:57
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 22, 2026
@ottobolyos
ottobolyos force-pushed the fix/jsonconditions-structural-rewrite branch from eaf2fbc to ff0658e Compare August 23, 2026 02:58
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 23, 2026
@ottobolyos
ottobolyos marked this pull request as ready for review August 26, 2026 04:15
…ers surface (RED)

Adds JsonConditionWrapperTests and rewrites JsonConditionsArrayShapeTests
to pin the cppagent JSON v2 array-of-single-key-wrappers wire shape via
the new JsonConditionWrapper + list-derived JsonConditions surface that
the following fix commit introduces.

New coverage in JsonConditionWrapperTests (24 tests):

* Four Of{Fault,Warning,Normal,Unavailable} factory contracts
  (each populates exactly one level, others null).
* Value accessor: null on empty, correct return for each of the four
  single-populated levels, and Fault > Warning > Normal > Unavailable
  precedence on multi-populated wrappers.
* Level accessor: null on empty, correct wire-property-name return
  for each of the four single-populated levels.
* ToObservation materializer: null on empty, correct level enum
  for each of the four single-populated levels.
* Wire envelope serialization: single-key output under sparse
  options, convenience-accessor suppression, empty-wrapper -> {},
  multi-populated documentation.
* Per-envelope deserialization: each of the four single-key inputs
  populates the matching property and leaves the other three null.

Rewritten JsonConditionsArrayShapeTests (23 tests):

* Three ctors covered: default (empty), copy-from-wrappers (insertion
  order preserved), and IEnumerable<IObservationOutput> (null-arg /
  empty / level-order emission / null-entry-skip).
* Observations computed accessor: empty container, materialization
  in insertion order across all four levels, empty-wrapper skip.
* Wire shape: [] on empty, single Normal wrapper, insertion order
  preserved across mixed levels, observation-ctor Fault-Warning-
  Normal-Unavailable level order, mixed-level array deserialization
  populates the correct wrapper property, round-trip byte-identical,
  root null <-> "null".

This commit is compile-error RED:
the tests reference JsonConditionWrapper, the list-shape JsonConditions,
and the AmE parameter names / XML-doc verbs that all land in the
following fix commit.
Deletes JsonConditionsConverter (~200 lines) and rewrites
JsonConditions as List<JsonConditionWrapper>-derived so the type
shape IS the wire shape. System.Text.Json's default serializer
handles both directions with no custom converter code.

New shape:

* JsonConditions : List<JsonConditionWrapper> — three ctors:
  parameterless, copy-from-wrappers, and IEnumerable<IObservationOutput>
  which preserves the historical Fault -> Warning -> Normal ->
  Unavailable level-order emission for byte-compatible wire output
  against the pre-refactor converter under sparse serialization
  options.
* JsonConditionWrapper — four nullable level properties with
  [JsonIgnore(WhenWritingNull)] for the single-key envelope,
  Value / Level convenience read-side accessors, ToObservation
  materializer, and OfFault / OfWarning / OfNormal / OfUnavailable
  factories.

Deletions:

* JsonConditionsConverter.cs — the entire class.
* [JsonConverter(typeof(JsonConditionsConverter))] attribute on
  JsonConditions.

Behavioral changes (intentional at 7.0 major-bump window):

* Legacy MTConnect JSON v1 object-keyed READ compat is dropped.
  The default List<T> deserializer only reads arrays; the pre-7.0
  fallback to {"Fault":[...], "Warning":[...]} envelope is removed.
* Mixed-level wire interleaving is no longer re-bucketed by level
  on write — insertion order is preserved end-to-end. Producers
  wanting the historical grouping can use the IEnumerable<
  IObservationOutput> ctor or sort explicitly.
* Strict malformed-wrapper rejection with named JsonException
  messages is dropped; default S.T.J tolerance applies.

Turns the RED tests from the preceding commit GREEN:
  47/47 on JsonCondition* filter; 391/391 on the full
  MTConnect.NET-JSON-cppagent-Tests suite (bluefin net8.0).

Closes dime-connector bug report TrakHound#15 on the mtc.net upstream side.
…ssor precedence

Closes coverage-FLOOR gaps left when the
old JsonConditionsConverter's explicit failure-path branches shifted
to implicit default-S.T.J behavior on the list-of-wrappers rewrite.

New regression pins in JsonConditionsArrayShapeTests (+16 test cases):

* Deserialize_unknown_level_property_is_silently_ignored — wire-shape
  drift pin: pre-7.0 the converter threw JsonException on any array
  entry with a non-{Fault,Warning,Normal,Unavailable} property name;
  the new impl silently drops the wrapper to all-null. A future
  UnmappedMemberHandling.Disallow swap would flip this test.
* Deserialize_non_array_root_throws_JsonException — parameterized over
  {123, "fault", true, false}; mirrors the removed "Unexpected token"
  branch for non-object roots (v1 object-shape drop is already pinned
  separately).
* Deserialize_scalar_or_array_element_throws_JsonException — over
  {[123], ["fault"], [true], [[]]}; mirrors the removed "Unexpected
  token '...' inside JsonConditions array; expected object wrapper"
  branch.
* Deserialize_malformed_wire_throws_JsonException — over four
  truncated / not-JSON bytes shapes; pins no silent-fallback
  empty-list result.
* RoundTrip_single_level_wrapper_preserves_level — parameterized over
  every ConditionLevel enum arm ({Fault, Warning, Normal, Unavailable})
  so each single-level round-trip has its own byte-identity assertion,
  satisfying enum-arm exhaustiveness on the wire layer.

New pins in JsonConditionWrapperTests (+2):

* Level_precedence_is_Fault_Warning_Normal_Unavailable — explicit
  Level-accessor precedence pin (previously only Value precedence
  was pinned; a future refactor could desync the two).
* ToObservation_precedence_is_Fault_Warning_Normal_Unavailable —
  same for the materializer, so all three read-side accessors have
  parallel precedence coverage.

68/68 on JsonCondition* filter (was 49/49).
- Ctor(IEnumerable<IObservationOutput>) buffers input once before the
  four level-partition passes; caller-supplied lazy / deferred
  enumerables (LINQ queries, generators) no longer get re-evaluated
  four times (HIGH from code-review + improvement agents).
- Hoist ConditionLevel names to compile-time constants via nameof —
  drops four enum ToString() allocations per ctor call (MEDIUM from
  improvement agent).
- Soften the "byte-identical on the wire" claim on the type's XML
  remark to acknowledge the JsonSerializerOptions precondition
  (MEDIUM from code-review agent).
- Fill missing <param> + <returns> blocks on OfFault/OfWarning/OfNormal/
  OfUnavailable + ToObservation + both ctors — 100 % XML-doc coverage
  floor (HIGH from documentation-audit agent).
- Style: drop System.Linq using in favor of Array.Empty<T>() +
  System.Func → Func (LOW from code-review agent).
@ottobolyos
ottobolyos force-pushed the fix/jsonconditions-structural-rewrite branch from ff0658e to 382a63a Compare August 26, 2026 04:21
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 26, 2026
…ntegration/up-to-pr-221

Cascade rebuild after upstream/master advanced to 0ddc3c6 (PR TrakHound#220 merge).
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 26, 2026
…ng PR test surfaces

integration-branch-only glue fix. PR TrakHound#221, TrakHound#223/TrakHound#224 (via TrakHound#233's
consolidation), and TrakHound#226 each add or touch test files written against
NUnit 3, whose bare-lambda Assert.Throws/DoesNotThrow calls become
ambiguous once PR TrakHound#239's NUnit 3->4 upgrade lands in the same tree.
Cast to Action explicitly at each affected call site and replace the
two CollectionAssert.AreEqual calls (removed in NUnit 4) with
Assert.That(..., Is.EqualTo(...)). None of the individual PR branches
need this fix standalone — each builds clean against current
upstream/master without NUnit 4 present.
@PatrickRitchie
PatrickRitchie merged commit dc7b100 into TrakHound:master Aug 26, 2026
13 checks passed
@github-project-automation github-project-automation Bot moved this from Reviewing to Done in MTConnect.NET-Development Aug 26, 2026
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 26, 2026
…n) cast for NUnit 4 (CS0121)

Cascade rebase of chore/nunit-4-upgrade onto master post-TrakHound#221 surfaced
four fresh CS0121 ambiguity errors in JsonConditionsArrayShapeTests
(added by TrakHound#221) where bare `() =>` lambdas passed to Assert.Throws<T>
are ambiguous between the TestDelegate and Action overloads once NUnit
is upgraded to 4.x. TestDelegate is obsolete-as-error in NUnit 4, so
apply the same (Action) cast pattern established for the campaign in
ae03e24 (AgentUuidValidationTests) at the four new sites:

  Streams/JsonConditionsArrayShapeTests.cs L428, L498, L516, L530
ottobolyos added a commit to ottobolyos/mtconnect.net that referenced this pull request Aug 27, 2026
…n) cast for NUnit 4 (CS0121)

Cascade rebase of chore/nunit-4-upgrade onto master post-TrakHound#221 surfaced
four fresh CS0121 ambiguity errors in JsonConditionsArrayShapeTests
(added by TrakHound#221) where bare `() =>` lambdas passed to Assert.Throws<T>
are ambiguous between the TestDelegate and Action overloads once NUnit
is upgraded to 4.x. TestDelegate is obsolete-as-error in NUnit 4, so
apply the same (Action) cast pattern established for the campaign in
ae03e24 (AgentUuidValidationTests) at the four new sites:

  Streams/JsonConditionsArrayShapeTests.cs L428, L498, L516, L530
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

2 participants