diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs
index f2ff438bb..ca25ebd36 100644
--- a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs
+++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs
@@ -1345,17 +1345,18 @@ public void WriteStatusCode(string fieldName, StatusCode value)
return;
}
+ // Verbose and NonReversible
+ PushStructure(fieldName);
if (value != StatusCodes.Good)
{
- PushStructure(fieldName);
WriteSimpleField("Code", value.Code.ToString(CultureInfo.InvariantCulture), EscapeOptions.NoFieldNameEscape);
string symbolicId = StatusCode.LookupSymbolicId(value.CodeBits);
if (!string.IsNullOrEmpty(symbolicId))
{
WriteSimpleField("Symbol", symbolicId, EscapeOptions.Quotes | EscapeOptions.NoFieldNameEscape);
}
- PopStructure();
}
+ PopStructure();
}
///
diff --git a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs
index dca47d47e..e6236c51e 100644
--- a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs
+++ b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs
@@ -1265,10 +1265,37 @@ public static string ReplaceDCLocalhost(string subjectName, string hostname = nu
///
public static string EscapeUri(string uri)
{
- if (!String.IsNullOrWhiteSpace(uri))
+ if (!string.IsNullOrWhiteSpace(uri))
{
- var builder = new UriBuilder(uri.Replace(";", "%3b"));
- return builder.Uri.AbsoluteUri;
+ // back compat: for not well formed Uri, fall back to legacy formatting behavior - see #2793
+ if (!Uri.IsWellFormedUriString(uri, UriKind.Absolute) ||
+ !Uri.TryCreate(uri.Replace(";", "%3b"), UriKind.Absolute, out Uri validUri))
+ {
+ var buffer = new StringBuilder();
+ foreach (char ch in uri)
+ {
+ switch (ch)
+ {
+ case ';':
+ case '%':
+ {
+ buffer.AppendFormat(CultureInfo.InvariantCulture, "%{0:X2}", Convert.ToInt16(ch));
+ break;
+ }
+
+ default:
+ {
+ buffer.Append(ch);
+ break;
+ }
+ }
+ }
+ return buffer.ToString();
+ }
+ else
+ {
+ return validUri.AbsoluteUri;
+ }
}
return String.Empty;
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/JsonEncoderTests.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/JsonEncoderTests.cs
index bbe1853b0..a15bcda1e 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/JsonEncoderTests.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/JsonEncoderTests.cs
@@ -374,13 +374,19 @@ private string BuildExpectedResponse(
if (jsonEncoding == JsonEncodingType.Compact || jsonEncoding == JsonEncodingType.Reversible)
{
oText = "0";
+ // default statuscode is not encoded
+ continue;
+ }
+ else if (jsonEncoding == JsonEncodingType.Verbose)
+ {
+ oText = "{}";
}
else
{
oText = "{\"Code\": 0,\"Symbol\":\"Good\"}";
// default statuscode is not encoded
+ continue;
}
- continue;
}
else if (property.Name == "Guid")
{
diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderTests.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderTests.cs
index b70368980..614be99ac 100644
--- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderTests.cs
+++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderTests.cs
@@ -286,8 +286,8 @@ public class JsonEncoderTests : EncoderCommon
$"{{\"IdType\":3,\"Id\":\"{s_byteString64}\",\"Namespace\":88}}", null,
$"\"ns=88;b={s_byteString64}\"", null},
- { BuiltInType.StatusCode, new StatusCode(StatusCodes.Good), null, null, null, null},
- { BuiltInType.StatusCode, new StatusCode(StatusCodes.Good), $"{StatusCodes.Good}", "", null, null, true},
+ { BuiltInType.StatusCode, new StatusCode(StatusCodes.Good), null, null, null, "{}"},
+ { BuiltInType.StatusCode, new StatusCode(StatusCodes.Good), $"{StatusCodes.Good}", "{}", null, "{}", true},
{ BuiltInType.StatusCode, new StatusCode(StatusCodes.BadBoundNotFound), $"{StatusCodes.BadBoundNotFound}",
$"{{\"Code\":{StatusCodes.BadBoundNotFound}, \"Symbol\":\"{nameof(StatusCodes.BadBoundNotFound)}\"}}"},
{ BuiltInType.StatusCode, new StatusCode(StatusCodes.BadCertificateInvalid),
@@ -352,6 +352,9 @@ public class JsonEncoderTests : EncoderCommon
{ BuiltInType.DataValue, new DataValue(), "{}", null},
{ BuiltInType.DataValue, new DataValue(StatusCodes.Good), "{}", null},
+ { BuiltInType.DataValue, new DataValue(StatusCodes.BadNotWritable),
+ $"{{\"StatusCode\":{StatusCodes.BadNotWritable}}}",
+ $"{{\"StatusCode\":{{\"Code\":{StatusCodes.BadNotWritable}, \"Symbol\":\"{nameof(StatusCodes.BadNotWritable)}\"}}}}"},
{ BuiltInType.Enumeration, (TestEnumType) 0, "0", "\"0\""},
{ BuiltInType.Enumeration, TestEnumType.Three, TestEnumType.Three.ToString("d"), $"\"{TestEnumType.Three}_{TestEnumType.Three.ToString("d")}\""},
@@ -372,6 +375,9 @@ public class JsonEncoderTests : EncoderCommon
"{\"Body\":{\"Foo\":\"bar_999\"}}", "{\"Foo\":\"bar_999\"}",
"{\"Body\":{\"Foo\":\"bar_999\"}}", null}
}.ToArray();
+
+ [DatapointSource]
+ public static StatusCode[] GoodAndBadStatusCodes = { StatusCodes.Good, StatusCodes.BadAlreadyExists };
#endregion
#region Test Setup
@@ -438,6 +444,7 @@ public void ForcePropertiesShouldThrow(JsonEncodingType jsonEncodingType)
Assert.Throws(() => encoder.ForceNamespaceUriForIndex1 = true);
Assert.Throws(() => encoder.IncludeDefaultNumberValues = true);
Assert.Throws(() => encoder.IncludeDefaultValues = true);
+ Assert.Throws(() => encoder.EncodeNodeIdAsString = false);
}
}
@@ -1162,6 +1169,45 @@ public void DateTimeEncodeStringTestCase(string dateTimeString)
DateTimeEncodeStringTest(dateTime);
}
+ ///
+ /// Validate that a ExpandedNodeId returns the expected
+ /// result for a not well formed Uri.
+ ///
+ [Test]
+ public void NotWellFormedUriInExpandedNodeId2String()
+ {
+ string namespaceUri = "KEPServerEX";
+ string nodeName = "Data Type Examples.16 Bit Device.K Registers.Double3";
+ String expectedNodeIdString = $"nsu={namespaceUri};s={nodeName}";
+ ExpandedNodeId expandedNodeId = new ExpandedNodeId(expectedNodeIdString);
+
+ string stringifiedExpandedNodId = expandedNodeId.ToString();
+ TestContext.Out.WriteLine(stringifiedExpandedNodId);
+ Assert.AreEqual(expectedNodeIdString, stringifiedExpandedNodId);
+ }
+
+ ///
+ /// Validate that a statuscode in a DataValue produces valid JSON.
+ ///
+ [Theory]
+ public void DataValueWithStatusCodes(
+ JsonEncodingType jsonEncodingType,
+ [ValueSource(nameof(GoodAndBadStatusCodes))] StatusCode statusCodeVariant,
+ [ValueSource(nameof(GoodAndBadStatusCodes))] StatusCode statusCode)
+ {
+ var dataValue = new DataValue() {
+ Value = new Variant(statusCodeVariant),
+ ServerTimestamp = DateTime.UtcNow,
+ StatusCode = statusCode
+ };
+ using (var jsonEncoder = new JsonEncoder(m_context, jsonEncodingType))
+ {
+ jsonEncoder.WriteDataValue("Data", dataValue);
+ var result = jsonEncoder.CloseAndReturnText();
+ PrettifyAndValidateJson(result, true);
+ }
+ }
+
///
/// Validate that the DateTime format strings return an equal result.
///