diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/TagEditors/AbCipTagConfigEditor.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/TagEditors/AbCipTagConfigEditor.razor index a845ebba..5a039f7e 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/TagEditors/AbCipTagConfigEditor.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/TagEditors/AbCipTagConfigEditor.razor @@ -28,8 +28,6 @@ _m = AbCipTagConfigModel.FromJson(ConfigJson); } - private static int ParseInt(object? v, int fallback = 0) => int.TryParse(v?.ToString(), out var i) ? i : fallback; - // TryParse so a bad/empty change value can never throw into the Blazor circuit — it falls back. private static TEnum ParseEnum(object? v, TEnum fallback) where TEnum : struct, Enum => Enum.TryParse(v?.ToString(), out var r) ? r : fallback; diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/TagEditors/AbLegacyTagConfigEditor.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/TagEditors/AbLegacyTagConfigEditor.razor index 66526cf0..77014b05 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/TagEditors/AbLegacyTagConfigEditor.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/TagEditors/AbLegacyTagConfigEditor.razor @@ -28,8 +28,6 @@ _m = AbLegacyTagConfigModel.FromJson(ConfigJson); } - private static int ParseInt(object? v, int fallback = 0) => int.TryParse(v?.ToString(), out var i) ? i : fallback; - // TryParse so a bad/empty change value can never throw into the Blazor circuit — it falls back. private static TEnum ParseEnum(object? v, TEnum fallback) where TEnum : struct, Enum => Enum.TryParse(v?.ToString(), out var r) ? r : fallback; diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/TagEditors/FocasTagConfigEditor.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/TagEditors/FocasTagConfigEditor.razor index cff7e878..8c7bd56e 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/TagEditors/FocasTagConfigEditor.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/TagEditors/FocasTagConfigEditor.razor @@ -28,8 +28,6 @@ _m = FocasTagConfigModel.FromJson(ConfigJson); } - private static int ParseInt(object? v, int fallback = 0) => int.TryParse(v?.ToString(), out var i) ? i : fallback; - // TryParse so a bad/empty change value can never throw into the Blazor circuit — it falls back. private static TEnum ParseEnum(object? v, TEnum fallback) where TEnum : struct, Enum => Enum.TryParse(v?.ToString(), out var r) ? r : fallback; diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/TagEditors/TwinCATTagConfigEditor.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/TagEditors/TwinCATTagConfigEditor.razor index b8371ab6..af11e1d2 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/TagEditors/TwinCATTagConfigEditor.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/TagEditors/TwinCATTagConfigEditor.razor @@ -28,8 +28,6 @@ _m = TwinCATTagConfigModel.FromJson(ConfigJson); } - private static int ParseInt(object? v, int fallback = 0) => int.TryParse(v?.ToString(), out var i) ? i : fallback; - // TryParse so a bad/empty change value can never throw into the Blazor circuit — it falls back. private static TEnum ParseEnum(object? v, TEnum fallback) where TEnum : struct, Enum => Enum.TryParse(v?.ToString(), out var r) ? r : fallback; diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/TagEditors/TagConfigJson.cs b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/TagEditors/TagConfigJson.cs index 33e15e73..673dd433 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/TagEditors/TagConfigJson.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/TagEditors/TagConfigJson.cs @@ -33,7 +33,10 @@ public static class TagConfigJson public static TEnum GetEnum(JsonObject o, string name, TEnum fallback) where TEnum : struct, Enum => GetString(o, name) is { } s && Enum.TryParse(s, ignoreCase: true, out var v) ? v : fallback; - /// Sets a string/number/enum-name value (enums via ToString()). Null removes the key. + /// Sets a string/number/enum-name value (enums via ToString()). A null value REMOVES the key, so it is omitted from the serialised JSON. public static void Set(JsonObject o, string name, object? value) - => o[name] = value is null ? null : JsonValue.Create(value is Enum e ? e.ToString() : value); + { + if (value is null) { o.Remove(name); return; } + o[name] = JsonValue.Create(value is Enum e ? e.ToString() : value); + } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/AbCipTagConfigModelTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/AbCipTagConfigModelTests.cs index ea5a8405..73fb1c41 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/AbCipTagConfigModelTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/AbCipTagConfigModelTests.cs @@ -79,4 +79,24 @@ public sealed class AbCipTagConfigModelTests { new AbCipTagConfigModel { TagPath = "Motor1.Status" }.Validate().ShouldBeNull(); } + + [Fact] + public void ToJson_omits_blank_deviceHostAddress() + { + var m = new AbCipTagConfigModel { TagPath = "Motor1.Status", DeviceHostAddress = "" }; + + var json = m.ToJson(); + + json.ShouldNotContain("deviceHostAddress"); + } + + [Fact] + public void ToJson_includes_deviceHostAddress_when_set() + { + var m = new AbCipTagConfigModel { TagPath = "Motor1.Status", DeviceHostAddress = "ab://host" }; + + var json = m.ToJson(); + + json.ShouldContain("\"deviceHostAddress\":\"ab://host\""); + } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/AbLegacyTagConfigModelTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/AbLegacyTagConfigModelTests.cs index f900a834..6552bcb5 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/AbLegacyTagConfigModelTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/AbLegacyTagConfigModelTests.cs @@ -79,4 +79,24 @@ public sealed class AbLegacyTagConfigModelTests { new AbLegacyTagConfigModel { Address = "N7:0" }.Validate().ShouldBeNull(); } + + [Fact] + public void ToJson_omits_blank_deviceHostAddress() + { + var m = new AbLegacyTagConfigModel { Address = "N7:0", DeviceHostAddress = "" }; + + var json = m.ToJson(); + + json.ShouldNotContain("deviceHostAddress"); + } + + [Fact] + public void ToJson_includes_deviceHostAddress_when_set() + { + var m = new AbLegacyTagConfigModel { Address = "N7:0", DeviceHostAddress = "ab://host" }; + + var json = m.ToJson(); + + json.ShouldContain("\"deviceHostAddress\":\"ab://host\""); + } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/FocasTagConfigModelTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/FocasTagConfigModelTests.cs index 03a1ff28..f7dbca89 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/FocasTagConfigModelTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/FocasTagConfigModelTests.cs @@ -79,4 +79,24 @@ public sealed class FocasTagConfigModelTests { new FocasTagConfigModel { Address = "R100" }.Validate().ShouldBeNull(); } + + [Fact] + public void ToJson_omits_blank_deviceHostAddress() + { + var m = new FocasTagConfigModel { Address = "R100", DeviceHostAddress = "" }; + + var json = m.ToJson(); + + json.ShouldNotContain("deviceHostAddress"); + } + + [Fact] + public void ToJson_includes_deviceHostAddress_when_set() + { + var m = new FocasTagConfigModel { Address = "R100", DeviceHostAddress = "ab://host" }; + + var json = m.ToJson(); + + json.ShouldContain("\"deviceHostAddress\":\"ab://host\""); + } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/TagConfigJsonTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/TagConfigJsonTests.cs index 87e2f0d9..e1d4e454 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/TagConfigJsonTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/TagConfigJsonTests.cs @@ -129,6 +129,7 @@ public sealed class TagConfigJsonTests TagConfigJson.Set(obj, "region", null); - TagConfigJson.GetString(obj, "region").ShouldBeNull(); + obj.ContainsKey("region").ShouldBeFalse(); + TagConfigJson.Serialize(obj).ShouldNotContain("region"); } } diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/TwinCATTagConfigModelTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/TwinCATTagConfigModelTests.cs index 435edc89..1871c850 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/TwinCATTagConfigModelTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/TwinCATTagConfigModelTests.cs @@ -79,4 +79,24 @@ public sealed class TwinCATTagConfigModelTests { new TwinCATTagConfigModel { SymbolPath = "MAIN.bStart" }.Validate().ShouldBeNull(); } + + [Fact] + public void ToJson_omits_blank_deviceHostAddress() + { + var m = new TwinCATTagConfigModel { SymbolPath = "MAIN.bStart", DeviceHostAddress = "" }; + + var json = m.ToJson(); + + json.ShouldNotContain("deviceHostAddress"); + } + + [Fact] + public void ToJson_includes_deviceHostAddress_when_set() + { + var m = new TwinCATTagConfigModel { SymbolPath = "MAIN.bStart", DeviceHostAddress = "ab://host" }; + + var json = m.ToJson(); + + json.ShouldContain("\"deviceHostAddress\":\"ab://host\""); + } }