Summary
MTConnect.NET-JSON-cppagent's JsonConditions class is a typed POCO with four named array-of-JsonCondition properties — Fault, Warning, Normal, Unavailable — each tagged with [JsonPropertyName(...)]. When serialised through System.Text.Json, that shape lands on the wire as:
"Condition": {
"Fault": [ … ],
"Warning": [ … ],
"Normal": [ … ],
"Unavailable": [ … ]
}
— the legacy MTConnect JSON v1 object-keyed-by-level shape. The cppagent JSON v2 wire form (which JSON-cppagent-mqtt formatter is named after) is an array of single-key wrapper objects keyed by level name:
"Condition": [
{ "Normal": { "dataItemId": "c1", … } },
{ "Warning": { "dataItemId": "c2", "type": "LOGIC_PROGRAM", "value": "check" } },
{ "Fault": { "dataItemId": "c3", … } },
{ "Unavailable": { "dataItemId": "c4", … } }
]
Strict cppagent JSON v2 consumers (downstream bridges, dashboards) reject the v1 shape because the property keys collide with the per-level wrapper grammar — they expect to walk the array and dispatch on each wrapper's single key. The result is a wire envelope that is labelled JSON-cppagent-mqtt but is not cppagent-compatible for any observation that carries Conditions.
Reproduction
Boot a JSON-cppagent-mqtt agent, publish any Condition observation, subscribe to MTConnect/Current/+, and inspect the resulting envelope:
{ "MTConnectStreams": { "Streams": { "DeviceStream": [ { "ComponentStream": [ {
"Condition": {
"Normal": [ { "dataItemId": "cond-1", "sequence": 42 } ]
}
} ] } ] } } }
vs. cppagent v2.7.0.7 in an identical configuration:
{ "MTConnectStreams": { "Streams": { "DeviceStream": [ { "ComponentStream": [ {
"Condition": [
{ "Normal": { "dataItemId": "cond-1", "sequence": 42 } }
]
} ] } ] } } }
Root cause
libraries/MTConnect.NET-JSON-cppagent/Streams/JsonConditions.cs:
public class JsonConditions
{
[JsonPropertyName("Fault")]
public IEnumerable<JsonCondition> Fault { get; set; }
[JsonPropertyName("Warning")]
public IEnumerable<JsonCondition> Warning { get; set; }
[JsonPropertyName("Normal")]
public IEnumerable<JsonCondition> Normal { get; set; }
[JsonPropertyName("Unavailable")]
public IEnumerable<JsonCondition> Unavailable { get; set; }
...
}
JsonComponentStream declares the property as:
[JsonPropertyName("Condition")]
public JsonConditions Conditions { get; set; }
Default System.Text.Json serialisation of a typed object with four properties produces the v1 object-keyed shape directly. There is no converter, no array projection.
Authority
XSD — MTConnectStreams_2.7.xsd, ConditionListType (verbatim)
<xs:complexType name='ConditionListType'>
<xs:sequence>
<xs:choice minOccurs='0' maxOccurs='unbounded'>
<xs:element name='Normal' type='ConditionType'/>
<xs:element name='Warning' type='ConditionType'/>
<xs:element name='Fault' type='ConditionType'/>
<xs:element name='Unavailable' type='ConditionType'/>
</xs:choice>
</xs:sequence>
</xs:complexType>
XML wire form is unambiguously a sequence (ordered list) of Normal | Warning | Fault | Unavailable elements — i.e. interleaved per observation, in observation order. The JSON v2 array-of-wrappers shape is the natural translation; the v1 object-keyed shape is an early MTConnect approximation that loses observation ordering.
Prose — MTConnect Standard Part 2, §13 "Condition"
A Condition observation is a discrete state transition belonging to one of the four levels. Each transition is a separate observation; the wire form must preserve the per-level dispatch + the per-observation identity (dataItemId, sequence, timestamp). The v1 object-keyed shape can only carry "all Normal", "all Warning", etc. — it cannot interleave levels in observation order, which is what cppagent's array form delivers.
cppagent reference (JSON v2 wire-shape reference implementation, NOT a spec source)
cppagent v2.7.0.7's printer/json_printer.cpp and the parity test fixtures in test_package/json_printer_stream_test.cpp (ConditionDataItem test cases) emit the array shape for every Condition observation, regardless of level distribution. The unit-test corpus in cppagent — which has been stable across v2.x — never produces the object-keyed shape that MT.NET emits.
Proposed fix
Add a JsonConverter<JsonConditions> that on Write projects the four named properties into an array of {Level: jsonCondition} wrappers, and on Read accepts both shapes for back-compat. Apply via [JsonConverter(typeof(JsonConditionsConverter))] on the JsonConditions class.
Impact
Any cppagent JSON v2 consumer (downstream bridge, dashboard, historian) that walks the Condition array and dispatches on each wrapper's single key — i.e. every consumer that follows the cppagent reference shape — fails on every Current envelope carrying a Condition emitted by JSON-cppagent-mqtt.
Summary
MTConnect.NET-JSON-cppagent'sJsonConditionsclass is a typed POCO with four named array-of-JsonConditionproperties —Fault,Warning,Normal,Unavailable— each tagged with[JsonPropertyName(...)]. When serialised throughSystem.Text.Json, that shape lands on the wire as:— the legacy MTConnect JSON v1 object-keyed-by-level shape. The cppagent JSON v2 wire form (which
JSON-cppagent-mqttformatter is named after) is an array of single-key wrapper objects keyed by level name:Strict cppagent JSON v2 consumers (downstream bridges, dashboards) reject the v1 shape because the property keys collide with the per-level wrapper grammar — they expect to walk the array and dispatch on each wrapper's single key. The result is a wire envelope that is labelled
JSON-cppagent-mqttbut is not cppagent-compatible for any observation that carries Conditions.Reproduction
Boot a
JSON-cppagent-mqttagent, publish any Condition observation, subscribe toMTConnect/Current/+, and inspect the resulting envelope:{ "MTConnectStreams": { "Streams": { "DeviceStream": [ { "ComponentStream": [ { "Condition": { "Normal": [ { "dataItemId": "cond-1", "sequence": 42 } ] } } ] } ] } } }vs. cppagent v2.7.0.7 in an identical configuration:
{ "MTConnectStreams": { "Streams": { "DeviceStream": [ { "ComponentStream": [ { "Condition": [ { "Normal": { "dataItemId": "cond-1", "sequence": 42 } } ] } ] } ] } } }Root cause
libraries/MTConnect.NET-JSON-cppagent/Streams/JsonConditions.cs:JsonComponentStreamdeclares the property as:Default
System.Text.Jsonserialisation of a typed object with four properties produces the v1 object-keyed shape directly. There is no converter, no array projection.Authority
XSD —
MTConnectStreams_2.7.xsd,ConditionListType(verbatim)XML wire form is unambiguously a sequence (ordered list) of
Normal | Warning | Fault | Unavailableelements — i.e. interleaved per observation, in observation order. The JSON v2 array-of-wrappers shape is the natural translation; the v1 object-keyed shape is an early MTConnect approximation that loses observation ordering.Prose — MTConnect Standard Part 2, §13 "Condition"
A Condition observation is a discrete state transition belonging to one of the four levels. Each transition is a separate observation; the wire form must preserve the per-level dispatch + the per-observation identity (dataItemId, sequence, timestamp). The v1 object-keyed shape can only carry "all Normal", "all Warning", etc. — it cannot interleave levels in observation order, which is what cppagent's array form delivers.
cppagent reference (JSON v2 wire-shape reference implementation, NOT a spec source)
cppagent v2.7.0.7's
printer/json_printer.cppand the parity test fixtures intest_package/json_printer_stream_test.cpp(ConditionDataItemtest cases) emit the array shape for every Condition observation, regardless of level distribution. The unit-test corpus in cppagent — which has been stable across v2.x — never produces the object-keyed shape that MT.NET emits.Proposed fix
Add a
JsonConverter<JsonConditions>that onWriteprojects the four named properties into an array of{Level: jsonCondition}wrappers, and onReadaccepts both shapes for back-compat. Apply via[JsonConverter(typeof(JsonConditionsConverter))]on theJsonConditionsclass.Impact
Any cppagent JSON v2 consumer (downstream bridge, dashboard, historian) that walks the
Conditionarray and dispatches on each wrapper's single key — i.e. every consumer that follows the cppagent reference shape — fails on every Current envelope carrying a Condition emitted byJSON-cppagent-mqtt.