Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions MTConnect.NET.sln
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Agent", "templates\mtconnec
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MTConnect.NET.Builder", "build\MTConnect.NET.Builder\MTConnect.NET.Builder.csproj", "{FC9965F9-63B4-3BE9-FD8E-28B7C8E19A19}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MTConnect.NET-JSON-cppagent-Tests", "tests\MTConnect.NET-JSON-cppagent-Tests\MTConnect.NET-JSON-cppagent-Tests.csproj", "{07AB00DA-2D48-4089-8F19-203F39B4DC99}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -423,6 +425,14 @@ Global
{FC9965F9-63B4-3BE9-FD8E-28B7C8E19A19}.Package|Any CPU.Build.0 = Debug|Any CPU
{FC9965F9-63B4-3BE9-FD8E-28B7C8E19A19}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC9965F9-63B4-3BE9-FD8E-28B7C8E19A19}.Release|Any CPU.Build.0 = Release|Any CPU
{07AB00DA-2D48-4089-8F19-203F39B4DC99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07AB00DA-2D48-4089-8F19-203F39B4DC99}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07AB00DA-2D48-4089-8F19-203F39B4DC99}.Docker|Any CPU.ActiveCfg = Debug|Any CPU
{07AB00DA-2D48-4089-8F19-203F39B4DC99}.Docker|Any CPU.Build.0 = Debug|Any CPU
{07AB00DA-2D48-4089-8F19-203F39B4DC99}.Package|Any CPU.ActiveCfg = Debug|Any CPU
{07AB00DA-2D48-4089-8F19-203F39B4DC99}.Package|Any CPU.Build.0 = Debug|Any CPU
{07AB00DA-2D48-4089-8F19-203F39B4DC99}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07AB00DA-2D48-4089-8F19-203F39B4DC99}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -468,6 +478,7 @@ Global
{24C98CF3-CC93-4696-A036-8FD1E16F2E7E} = {FFF032D3-7446-4CAF-A3E3-CF9C4E1A5DCC}
{FF3FACB1-C470-4C7F-9A4B-F364BE1E32B3} = {D7873DF2-16DB-4B19-A100-C0089DF37488}
{FC9965F9-63B4-3BE9-FD8E-28B7C8E19A19} = {BBF53739-168D-4635-8595-083AC0C65E4C}
{07AB00DA-2D48-4089-8F19-203F39B4DC99} = {14375E03-6BF8-45E6-B868-D2399368992B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CC13D3AD-18BF-4695-AB2A-087EF0885B20}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace MTConnect.Streams.Json
{
[System.Text.Json.Serialization.JsonConverter(typeof(JsonConditionsConverter))]
public class JsonConditions
{
[JsonIgnore]
Expand Down Expand Up @@ -113,5 +114,5 @@ public JsonConditions(IEnumerable<IObservationOutput> observations)
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Copyright (c) 2023 TrakHound Inc., All Rights Reserved.
// TrakHound Inc. licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace MTConnect.Streams.Json
{
// Serializes JsonConditions in the cppagent JSON v2 wire shape: an
// ordered array of single-key wrapper objects, one per Condition entry
// (e.g. [{"Normal": {...}}, {"Warning": {...}}]). The XSD
// ConditionListType is <xs:sequence><xs:choice maxOccurs='unbounded'>
// of Normal|Warning|Fault|Unavailable, so the wire shape preserves
// observation order. The legacy MTConnect JSON v1 object-keyed shape
// ({"Fault": [...], "Warning": [...], ...}) is still accepted on the
// read path for back-compat.
internal sealed class JsonConditionsConverter : JsonConverter<JsonConditions>
{
private const string FaultLevel = "Fault";
private const string WarningLevel = "Warning";
private const string NormalLevel = "Normal";
private const string UnavailableLevel = "Unavailable";

public override JsonConditions Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.Null:
return null;

case JsonTokenType.StartArray:
return ReadArrayShape(ref reader, options);

case JsonTokenType.StartObject:
return ReadObjectShape(ref reader, options);

default:
throw new JsonException(
$"Unexpected token '{reader.TokenType}' when reading JsonConditions; expected array, object, or null.");
}
}

public override void Write(Utf8JsonWriter writer, JsonConditions value, JsonSerializerOptions options)
{
if (value == null)
{
writer.WriteNullValue();
return;
}

writer.WriteStartArray();

WriteLevel(writer, FaultLevel, value.Fault, options);
WriteLevel(writer, WarningLevel, value.Warning, options);
WriteLevel(writer, NormalLevel, value.Normal, options);
WriteLevel(writer, UnavailableLevel, value.Unavailable, options);

writer.WriteEndArray();
}

private static void WriteLevel(Utf8JsonWriter writer, string levelName, IEnumerable<JsonCondition> entries, JsonSerializerOptions options)
{
if (entries == null) return;

foreach (var entry in entries)
{
writer.WriteStartObject();
writer.WritePropertyName(levelName);
JsonSerializer.Serialize(writer, entry, options);
writer.WriteEndObject();
}
}

private static JsonConditions ReadArrayShape(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
var faults = new List<JsonCondition>();
var warnings = new List<JsonCondition>();
var normals = new List<JsonCondition>();
var unavailables = new List<JsonCondition>();

while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray) break;

if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException(
$"Unexpected token '{reader.TokenType}' inside JsonConditions array; expected object wrapper.");
}

if (!reader.Read() || reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException("Expected property name inside JsonConditions wrapper object.");
}

var levelName = reader.GetString();
reader.Read();
var entry = JsonSerializer.Deserialize<JsonCondition>(ref reader, options);

if (!reader.Read() || reader.TokenType != JsonTokenType.EndObject)
{
throw new JsonException("Expected end of JsonConditions wrapper object after entry.");
}

switch (levelName)
{
case FaultLevel:
faults.Add(entry);
break;
case WarningLevel:
warnings.Add(entry);
break;
case NormalLevel:
normals.Add(entry);
break;
case UnavailableLevel:
unavailables.Add(entry);
break;
default:
throw new JsonException(
$"Unknown Condition level '{levelName}' in JsonConditions array; expected Fault, Warning, Normal, or Unavailable.");
}
}

return new JsonConditions
{
Fault = faults.Count > 0 ? faults : null,
Warning = warnings.Count > 0 ? warnings : null,
Normal = normals.Count > 0 ? normals : null,
Unavailable = unavailables.Count > 0 ? unavailables : null,
};
}

private static JsonConditions ReadObjectShape(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
var result = new JsonConditions();

while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject) break;

if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException(
$"Unexpected token '{reader.TokenType}' inside JsonConditions object; expected property name.");
}

var levelName = reader.GetString();
reader.Read();
var entries = JsonSerializer.Deserialize<List<JsonCondition>>(ref reader, options);

switch (levelName)
{
case FaultLevel:
result.Fault = entries;
break;
case WarningLevel:
result.Warning = entries;
break;
case NormalLevel:
result.Normal = entries;
break;
case UnavailableLevel:
result.Unavailable = entries;
break;
default:
throw new JsonException(
$"Unknown Condition level '{levelName}' in JsonConditions object; expected Fault, Warning, Normal, or Unavailable.");
}
}

return result;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>MTConnect.NET_JSON_cppagent_Tests</RootNamespace>
<Nullable>disable</Nullable>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="coverlet.collector" Version="3.1.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\libraries\MTConnect.NET-JSON-cppagent\MTConnect.NET-JSON-cppagent.csproj" />
</ItemGroup>

</Project>
Loading