fix(commons): LoadLegacy handles mixed-type JSON values (number/bool/string)

This commit is contained in:
Joseph Doherty
2026-05-12 02:08:32 -04:00
parent cfb90d2078
commit 084da55ad6
2 changed files with 42 additions and 2 deletions

View File

@@ -118,8 +118,27 @@ public static class OpcUaEndpointConfigSerializer
private static OpcUaEndpointConfig LoadLegacy(string json)
{
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json)
?? new Dictionary<string, string>();
using var doc = JsonDocument.Parse(json);
if (doc.RootElement.ValueKind != JsonValueKind.Object)
throw new JsonException("Legacy JSON must be a flat object.");
var dict = new Dictionary<string, string>();
foreach (var prop in doc.RootElement.EnumerateObject())
{
// JsonElement.ToString() returns the raw value for primitives:
// "1000" for numbers, "true"/"false" for booleans, the string content
// for strings. Nested objects/arrays serialize to their JSON; we don't
// expect those in the legacy flat-dict shape, but FromFlatDict will
// simply ignore unknown keys.
dict[prop.Name] = prop.Value.ValueKind switch
{
JsonValueKind.String => prop.Value.GetString() ?? "",
JsonValueKind.Number => prop.Value.GetRawText(),
JsonValueKind.True => "True",
JsonValueKind.False => "False",
_ => prop.Value.ToString()
};
}
return FromFlatDict(dict);
}

View File

@@ -114,6 +114,27 @@ public class OpcUaEndpointConfigSerializerTests
Assert.Equal("opc.tcp://x:4840", config.EndpointUrl);
}
[Fact]
public void Deserialize_LegacyWithMixedTypeValues_PreservesAllFields()
{
// Real-world legacy producer emitted mixed JSON value types:
// string for endpoint, number for publishInterval, etc.
var legacyJson = """
{"endpoint":"opc.tcp://scadalink-opcua:50000","securityMode":"None","publishInterval":1000,"AutoAcceptUntrustedCerts":false,"SessionTimeoutMs":45000}
""";
var (config, isLegacy) = OpcUaEndpointConfigSerializer.Deserialize(legacyJson);
Assert.True(isLegacy);
Assert.Equal("opc.tcp://scadalink-opcua:50000", config.EndpointUrl);
Assert.Equal(OpcUaSecurityMode.None, config.SecurityMode);
Assert.False(config.AutoAcceptUntrustedCerts);
Assert.Equal(45000, config.SessionTimeoutMs);
// publishInterval is not a recognized key (the real key is PublishingIntervalMs);
// FromFlatDict ignores unknown keys, so PublishingIntervalMs stays at its POCO default of 1000.
Assert.Equal(1000, config.PublishingIntervalMs);
}
[Theory]
[InlineData("not json at all")]
[InlineData("[1,2,3]")]