/* ========================================================================
* Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.Extensions.Logging;
#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
using System.Buffers;
#endif
#pragma warning disable CS0618 // Type or member is obsolete
namespace Opc.Ua.PubSub.Encoding
{
///
/// Writes objects to a JSON stream.
///
internal class PubSubJsonEncoder : IEncoder
{
private const int kStreamWriterBufferSize = 1024;
private const string kQuotationColon = "\":";
private const char kComma = ',';
private const char kQuotation = '\"';
private const char kBackslash = '\\';
private const char kLeftCurlyBrace = '{';
private const char kRightCurlyBrace = '}';
private const char kLeftSquareBracket = '[';
private const char kRightSquareBracket = ']';
private static readonly UTF8Encoding s_utf8Encoding = new(false);
private const string kNull = "null";
private Stream? m_stream;
private MemoryStream? m_memoryStream;
private StreamWriter m_writer = null!;
private readonly Stack m_namespaces = [];
private bool m_commaRequired;
private bool m_inVariantWithEncoding;
private ushort[]? m_namespaceMappings;
private ushort[]? m_serverMappings;
private uint m_nestingLevel;
private readonly bool m_topLevelIsArray;
private readonly ILogger m_logger = null!;
private bool m_levelOneSkipped;
private bool m_dontWriteClosing;
private readonly bool m_leaveOpen;
private bool m_forceNamespaceUri;
private bool m_forceNamespaceUriForIndex1;
private bool m_includeDefaultNumberValues;
private bool m_includeDefaultValues;
private bool m_encodeNodeIdAsString;
[Flags]
private enum EscapeOptions
{
None = 0,
Quotes = 1,
NoValueEscape = 2,
NoFieldNameEscape = 4
}
///
/// Initializes the object with default values.
/// Selects the reversible or non reversible encoding.
///
public PubSubJsonEncoder(IServiceMessageContext context, bool useReversibleEncoding)
: this(
context,
useReversibleEncoding
? PubSubJsonEncoding.Reversible
: PubSubJsonEncoding.NonReversible,
false,
null,
false)
{
}
///
/// Initializes the object with default values.
/// Selects the reversible or non reversible encoding.
///
public PubSubJsonEncoder(
IServiceMessageContext context,
bool useReversibleEncoding,
bool topLevelIsArray = false,
Stream? stream = null,
bool leaveOpen = false,
int streamSize = kStreamWriterBufferSize)
: this(
context,
useReversibleEncoding
? PubSubJsonEncoding.Reversible
: PubSubJsonEncoding.NonReversible,
topLevelIsArray,
stream,
leaveOpen,
streamSize)
{
}
///
/// Initializes the object with default values.
/// Selects the reversible or non reversible encoding.
///
public PubSubJsonEncoder(
IServiceMessageContext context,
bool useReversibleEncoding,
StreamWriter streamWriter,
bool topLevelIsArray = false)
: this(
context,
useReversibleEncoding
? PubSubJsonEncoding.Reversible
: PubSubJsonEncoding.NonReversible,
streamWriter,
topLevelIsArray)
{
}
///
/// Initializes the object with default values.
///
public PubSubJsonEncoder(
IServiceMessageContext context,
PubSubJsonEncoding encoding,
bool topLevelIsArray = false,
Stream? stream = null,
bool leaveOpen = false,
int streamSize = kStreamWriterBufferSize)
: this(encoding)
{
Context = context;
m_logger = context.Telemetry.CreateLogger();
m_stream = stream;
m_leaveOpen = leaveOpen;
m_topLevelIsArray = topLevelIsArray;
if (m_stream == null)
{
m_memoryStream = new MemoryStream();
m_writer = new StreamWriter(m_memoryStream, s_utf8Encoding, streamSize, false);
m_leaveOpen = false;
}
else
{
m_writer = new StreamWriter(m_stream, s_utf8Encoding, streamSize, m_leaveOpen);
}
InitializeWriter();
}
///
/// Initializes the object with default values.
///
public PubSubJsonEncoder(
IServiceMessageContext context,
PubSubJsonEncoding encoding,
StreamWriter writer,
bool topLevelIsArray = false)
: this(encoding)
{
Context = context;
m_logger = context.Telemetry.CreateLogger();
m_writer = writer;
m_topLevelIsArray = topLevelIsArray;
if (m_writer == null)
{
m_stream = new MemoryStream();
m_writer = new StreamWriter(m_stream, s_utf8Encoding, kStreamWriterBufferSize);
}
InitializeWriter();
}
///
/// Sets default values.
///
private PubSubJsonEncoder(PubSubJsonEncoding encoding)
{
// defaults for JSON encoding
EncodingToUse = encoding;
if (encoding is PubSubJsonEncoding.Reversible or PubSubJsonEncoding.NonReversible)
{
// defaults for reversible and non reversible JSON encoding
// -- encode namespace index for reversible encoding / uri for non reversible
// -- do not include default values for reversible encoding
// -- include default values for non reversible encoding
m_forceNamespaceUri =
m_forceNamespaceUriForIndex1 =
m_includeDefaultValues =
encoding == PubSubJsonEncoding.NonReversible;
m_includeDefaultNumberValues = true;
m_encodeNodeIdAsString = false;
}
else
{
// defaults for compact and verbose JSON encoding, properties throw exception if modified
m_forceNamespaceUri = true;
m_forceNamespaceUriForIndex1 = true;
m_includeDefaultValues = encoding == PubSubJsonEncoding.Verbose;
m_includeDefaultNumberValues = encoding == PubSubJsonEncoding.Verbose;
m_encodeNodeIdAsString = true;
}
m_inVariantWithEncoding = IncludeDefaultValues;
}
///
/// Initialize Writer.
///
private void InitializeWriter()
{
if (m_topLevelIsArray)
{
m_writer.Write(kLeftSquareBracket);
}
else
{
m_writer.Write(kLeftCurlyBrace);
}
}
///
/// Encodes a message in a stream.
///
/// is null.
/// is null.
/// is null.
public static ArraySegment EncodeMessage(
IEncodeable message,
byte[] buffer,
IServiceMessageContext context)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
using var stream = new MemoryStream(buffer, true);
using var encoder = new PubSubJsonEncoder(context, true, false, stream);
// encode message
encoder.EncodeMessage(message, message.TypeId);
int length = encoder.Close();
return new ArraySegment(buffer, 0, length);
}
///
public void EncodeMessage(T message, ExpandedNodeId encodeableTypeId)
where T : IEncodeable
{
if (EqualityComparer.Default.Equals(message, default!))
{
throw new ArgumentNullException(nameof(message));
}
// convert the namespace uri to an index.
var typeId = ExpandedNodeId.ToNodeId(encodeableTypeId, Context.NamespaceUris);
// write the type id.
WriteNodeId("TypeId", typeId);
// write the message.
WriteEncodeable("Body", message, message.GetType());
}
///
public void EncodeMessage(T message) where T : IEncodeable, new()
{
if (EqualityComparer.Default.Equals(message, default!))
{
throw new ArgumentNullException(nameof(message));
}
// convert the namespace uri to an index.
var typeId = ExpandedNodeId.ToNodeId(message.TypeId, Context.NamespaceUris);
// write the type id.
WriteNodeId("TypeId", typeId);
// write the message.
WriteEncodeable("Body", message, message.GetType());
}
///
/// Initializes the tables used to map namespace and server uris during encoding.
///
/// The namespaces URIs referenced by the data being encoded.
/// The server URIs referenced by the data being encoded.
public void SetMappingTables(NamespaceTable namespaceUris, StringTable serverUris)
{
m_namespaceMappings = null;
if (namespaceUris != null && Context.NamespaceUris != null)
{
m_namespaceMappings = namespaceUris.CreateMapping(Context.NamespaceUris, false);
}
m_serverMappings = null;
if (serverUris != null && Context.ServerUris != null)
{
m_serverMappings = serverUris.CreateMapping(Context.ServerUris, false);
}
}
///
/// Completes writing and returns the JSON text.
///
/// The underlying stream is not a .
public string CloseAndReturnText()
{
try
{
InternalClose(false);
if (m_memoryStream == null)
{
if (m_stream is MemoryStream memoryStream)
{
return System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());
}
throw new NotSupportedException(
"Cannot get text from external stream. Use Close or MemoryStream instead.");
}
return System.Text.Encoding.UTF8.GetString(m_memoryStream.ToArray());
}
finally
{
m_writer?.Dispose();
m_writer = null!;
}
}
///
/// Completes writing and returns the text length.
/// The StreamWriter is disposed.
///
public int Close()
{
return InternalClose(true);
}
///
/// Frees any unmanaged resources.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// An overrideable version of the Dispose.
///
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (m_writer != null)
{
InternalClose(true);
m_writer = null!;
}
if (!m_leaveOpen)
{
m_memoryStream?.Dispose();
m_stream?.Dispose();
m_memoryStream = null;
m_stream = null;
}
}
}
///
public PubSubJsonEncoding EncodingToUse { get; private set; }
///
public bool SuppressArtifacts { get; set; }
///
public void PushStructure(string? fieldName)
{
m_nestingLevel++;
if (m_commaRequired)
{
m_writer.Write(kComma);
}
if (!string.IsNullOrEmpty(fieldName))
{
EscapeString(fieldName);
m_writer.Write(kQuotationColon);
}
else if (!m_commaRequired)
{
if (m_nestingLevel == 1 && !m_topLevelIsArray)
{
m_levelOneSkipped = true;
return;
}
}
m_commaRequired = false;
m_writer.Write(kLeftCurlyBrace);
}
///
public void PushArray(string? fieldName)
{
m_nestingLevel++;
if (m_commaRequired)
{
m_writer.Write(kComma);
}
if (!string.IsNullOrEmpty(fieldName))
{
EscapeString(fieldName);
m_writer.Write(kQuotationColon);
}
else if (!m_commaRequired)
{
if (m_nestingLevel == 1 && !m_topLevelIsArray)
{
m_levelOneSkipped = true;
return;
}
}
m_commaRequired = false;
m_writer.Write(kLeftSquareBracket);
}
///
public void PopStructure()
{
if (m_nestingLevel > 1 ||
m_topLevelIsArray ||
(m_nestingLevel == 1 && !m_levelOneSkipped))
{
m_writer.Write(kRightCurlyBrace);
m_commaRequired = true;
}
m_nestingLevel--;
}
///
public void PopArray()
{
if (m_nestingLevel > 1 ||
m_topLevelIsArray ||
(m_nestingLevel == 1 && !m_levelOneSkipped))
{
m_writer.Write(kRightSquareBracket);
m_commaRequired = true;
}
m_nestingLevel--;
}
///
[Obsolete(
"Non/Reversible encoding is deprecated. Use UsingAlternateEncoding instead to support new encoding types."
)]
public void UsingReversibleEncoding(
Action action,
string fieldName,
T value,
bool useReversibleEncoding)
{
PubSubJsonEncoding currentValue = EncodingToUse;
try
{
EncodingToUse = useReversibleEncoding
? PubSubJsonEncoding.Reversible
: PubSubJsonEncoding.NonReversible;
action(fieldName, value);
}
finally
{
EncodingToUse = currentValue;
}
}
///
public void UsingAlternateEncoding(
Action action,
string fieldName,
T value,
PubSubJsonEncoding useEncodingType)
{
PubSubJsonEncoding currentValue = EncodingToUse;
try
{
EncodingToUse = useEncodingType;
action(fieldName, value);
}
finally
{
EncodingToUse = currentValue;
}
}
///
public void WriteSwitchField(uint switchField, out string? fieldName)
{
fieldName = null;
switch (EncodingToUse)
{
case PubSubJsonEncoding.Compact:
if (SuppressArtifacts)
{
return;
}
break;
case PubSubJsonEncoding.Reversible:
fieldName = "Value";
break;
case PubSubJsonEncoding.Verbose:
case PubSubJsonEncoding.NonReversible:
return;
default:
throw ServiceResultException.Unexpected(
$"Unexpected Encoding type {EncodingToUse}");
}
WriteUInt32("SwitchField", switchField);
}
///
public void WriteEncodingMask(uint encodingMask)
{
if ((!SuppressArtifacts && EncodingToUse == PubSubJsonEncoding.Compact) ||
EncodingToUse == PubSubJsonEncoding.Reversible)
{
WriteUInt32("EncodingMask", encodingMask);
}
}
///
public EncodingType EncodingType => EncodingType.Json;
///
public bool CanOmitFields => true;
///
public bool UseReversibleEncoding => EncodingToUse != PubSubJsonEncoding.NonReversible;
///
/// The message context associated with the encoder.
///
public IServiceMessageContext Context { get; } = null!;
///
/// The Json encoder to encoder namespace URI instead of
/// namespace Index in NodeIds.
///
public bool ForceNamespaceUri
{
get => m_forceNamespaceUri;
set => m_forceNamespaceUri = ThrowIfCompactOrVerbose(value);
}
///
/// The Json encoder to encode namespace URI for all
/// namespaces
///
public bool ForceNamespaceUriForIndex1
{
get => m_forceNamespaceUriForIndex1;
set => m_forceNamespaceUriForIndex1 = ThrowIfCompactOrVerbose(value);
}
///
/// The Json encoder default value option.
///
public bool IncludeDefaultValues
{
get => m_includeDefaultValues;
set => m_includeDefaultValues = ThrowIfCompactOrVerbose(value);
}
///
/// The Json encoder default value option for numbers.
///
public bool IncludeDefaultNumberValues
{
get => m_includeDefaultNumberValues || m_includeDefaultValues;
set => m_includeDefaultNumberValues = ThrowIfCompactOrVerbose(value);
}
///
/// The Json encoder default encoding for NodeId as string or object.
///
public bool EncodeNodeIdAsString
{
get => m_encodeNodeIdAsString;
set => m_encodeNodeIdAsString = ThrowIfCompactOrVerbose(value);
}
///
public void PushNamespace(string namespaceUri)
{
m_namespaces.Push(namespaceUri);
}
///
public void PopNamespace()
{
m_namespaces.Pop();
}
private static readonly char[] s_specialChars
= [kQuotation, kBackslash, '\n', '\r', '\t', '\b', '\f'];
private static readonly char[] s_substitution
= [kQuotation, kBackslash, 'n', 'r', 't', 'b', 'f'];
#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
///
/// Using a span to escape the string, write strings to stream writer if possible.
///
private void EscapeString(ReadOnlySpan value)
{
int lastOffset = 0;
m_writer.Write(kQuotation);
for (int i = 0; i < value.Length; i++)
{
bool found = false;
char ch = value[i];
for (int ii = 0; ii < s_specialChars.Length; ii++)
{
if (s_specialChars[ii] == ch)
{
WriteSpan(ref lastOffset, value, i);
m_writer.Write('\\');
m_writer.Write(s_substitution[ii]);
found = true;
break;
}
}
if (!found && ch < 32)
{
WriteSpan(ref lastOffset, value, i);
m_writer.Write('\\');
m_writer.Write('u');
m_writer.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture));
}
}
if (lastOffset == 0)
{
m_writer.Write(value);
}
else
{
WriteSpan(ref lastOffset, value, value.Length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteSpan(ref int lastOffset, ReadOnlySpan valueSpan, int index)
{
if (lastOffset < index - 2)
{
m_writer.Write(valueSpan[lastOffset..index]);
}
else
{
while (lastOffset < index)
{
m_writer.Write(valueSpan[lastOffset++]);
}
}
lastOffset = index + 1;
}
#else
///
/// Escapes a string and writes it to the stream.
///
private void EscapeString(string? value)
{
m_writer.Write(kQuotation);
foreach (char ch in value!)
{
bool found = false;
for (int ii = 0; ii < s_specialChars.Length; ii++)
{
if (s_specialChars[ii] == ch)
{
m_writer.Write(kBackslash);
m_writer.Write(s_substitution[ii]);
found = true;
break;
}
}
if (!found)
{
if (ch < 32)
{
m_writer.Write(kBackslash);
m_writer.Write('u');
m_writer.Write(((int)ch).ToString("X4", CultureInfo.InvariantCulture));
continue;
}
m_writer.Write(ch);
}
}
}
#endif
private void WriteSimpleFieldNull(string? fieldName)
{
if (string.IsNullOrEmpty(fieldName))
{
if (m_commaRequired)
{
m_writer.Write(kComma);
}
m_writer.Write(kNull);
m_commaRequired = true;
}
}
#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
private void WriteSimpleField(
string? fieldName,
string? value,
EscapeOptions options = EscapeOptions.None)
{
// unlike Span, Span can not become null, handle the case here
if (value == null)
{
WriteSimpleFieldNull(fieldName);
return;
}
WriteSimpleFieldAsSpan(fieldName, value.AsSpan(), options);
}
private void WriteSimpleFieldAsSpan(
string? fieldName,
ReadOnlySpan value,
EscapeOptions options)
{
if (!string.IsNullOrEmpty(fieldName))
{
if (m_commaRequired)
{
m_writer.Write(kComma);
}
if ((options & EscapeOptions.NoFieldNameEscape) == EscapeOptions.NoFieldNameEscape)
{
m_writer.Write(kQuotation);
m_writer.Write(fieldName);
}
else
{
EscapeString(fieldName);
}
m_writer.Write(kQuotationColon);
}
else if (m_commaRequired)
{
m_writer.Write(kComma);
}
if ((options & EscapeOptions.Quotes) == EscapeOptions.Quotes)
{
if ((options & EscapeOptions.NoValueEscape) == EscapeOptions.NoValueEscape)
{
m_writer.Write(kQuotation);
m_writer.Write(value);
}
else
{
EscapeString(value);
}
m_writer.Write(kQuotation);
}
else
{
m_writer.Write(value);
}
m_commaRequired = true;
}
#else
private void WriteSimpleField(
string? fieldName,
string? value,
EscapeOptions options = EscapeOptions.None)
{
if (!string.IsNullOrEmpty(fieldName))
{
if (value == null)
{
return;
}
if (m_commaRequired)
{
m_writer.Write(kComma);
}
if ((options & EscapeOptions.NoFieldNameEscape) == EscapeOptions.NoFieldNameEscape)
{
m_writer.Write(kQuotation);
m_writer.Write(fieldName);
}
else
{
EscapeString(fieldName);
}
m_writer.Write(kQuotationColon);
}
else if (m_commaRequired)
{
m_writer.Write(kComma);
}
if (value != null)
{
if ((options & EscapeOptions.Quotes) == EscapeOptions.Quotes)
{
if ((options & EscapeOptions.NoValueEscape) == EscapeOptions.NoValueEscape)
{
m_writer.Write(kQuotation);
m_writer.Write(value);
}
else
{
EscapeString(value);
}
m_writer.Write(kQuotation);
}
else
{
m_writer.Write(value);
}
}
else
{
m_writer.Write(kNull);
}
m_commaRequired = true;
}
#endif
///
/// Writes a boolean to the stream.
///
public void WriteBoolean(string? fieldName, bool value)
{
if (fieldName != null && !IncludeDefaultNumberValues && !value)
{
return;
}
if (value)
{
WriteSimpleField(fieldName, "true");
}
else
{
WriteSimpleField(fieldName, "false");
}
}
///
/// Writes a sbyte to the stream.
///
public void WriteSByte(string? fieldName, sbyte value)
{
if (fieldName != null && !IncludeDefaultNumberValues && value == 0)
{
return;
}
WriteSimpleField(fieldName, value.ToString(CultureInfo.InvariantCulture));
}
///
/// Writes a byte to the stream.
///
public void WriteByte(string? fieldName, byte value)
{
if (fieldName != null && !IncludeDefaultNumberValues && value == 0)
{
return;
}
WriteSimpleField(fieldName, value.ToString(CultureInfo.InvariantCulture));
}
///
/// Writes a short to the stream.
///
public void WriteInt16(string? fieldName, short value)
{
if (fieldName != null && !IncludeDefaultNumberValues && value == 0)
{
return;
}
WriteSimpleField(fieldName, value.ToString(CultureInfo.InvariantCulture));
}
///
/// Writes a ushort to the stream.
///
public void WriteUInt16(string? fieldName, ushort value)
{
if (fieldName != null && !IncludeDefaultNumberValues && value == 0)
{
return;
}
WriteSimpleField(fieldName, value.ToString(CultureInfo.InvariantCulture));
}
///
/// Writes an int to the stream.
///
public void WriteInt32(string? fieldName, int value)
{
if (fieldName != null && !IncludeDefaultNumberValues && value == 0)
{
return;
}
WriteSimpleField(fieldName, value.ToString(CultureInfo.InvariantCulture));
}
///
/// Writes a uint to the stream.
///
public void WriteUInt32(string? fieldName, uint value)
{
if (fieldName != null && !IncludeDefaultNumberValues && value == 0)
{
return;
}
WriteSimpleField(fieldName, value.ToString(CultureInfo.InvariantCulture));
}
///
/// Writes a long to the stream.
///
public void WriteInt64(string? fieldName, long value)
{
if (fieldName != null && !IncludeDefaultNumberValues && value == 0)
{
return;
}
WriteSimpleField(
fieldName,
value.ToString(CultureInfo.InvariantCulture),
EscapeOptions.Quotes);
}
///
/// Writes a ulong to the stream.
///
public void WriteUInt64(string? fieldName, ulong value)
{
if (fieldName != null && !IncludeDefaultNumberValues && value == 0)
{
return;
}
WriteSimpleField(
fieldName,
value.ToString(CultureInfo.InvariantCulture),
EscapeOptions.Quotes);
}
///
/// Writes a float to the stream.
///
public void WriteFloat(string? fieldName, float value)
{
if (fieldName != null &&
!IncludeDefaultNumberValues &&
(value > -float.Epsilon) &&
(value < float.Epsilon))
{
return;
}
if (float.IsNaN(value))
{
WriteSimpleField(fieldName, "\"NaN\"");
}
else if (float.IsPositiveInfinity(value))
{
WriteSimpleField(fieldName, "\"Infinity\"");
}
else if (float.IsNegativeInfinity(value))
{
WriteSimpleField(fieldName, "\"-Infinity\"");
}
else
{
WriteSimpleField(fieldName, value.ToString("R", CultureInfo.InvariantCulture));
}
}
///
/// Writes a double to the stream.
///
public void WriteDouble(string? fieldName, double value)
{
if (fieldName != null &&
!IncludeDefaultNumberValues &&
(value > -double.Epsilon) &&
(value < double.Epsilon))
{
return;
}
if (double.IsNaN(value))
{
WriteSimpleField(fieldName, "\"NaN\"");
}
else if (double.IsPositiveInfinity(value))
{
WriteSimpleField(fieldName, "\"Infinity\"");
}
else if (double.IsNegativeInfinity(value))
{
WriteSimpleField(fieldName, "\"-Infinity\"");
}
else
{
WriteSimpleField(fieldName, value.ToString("R", CultureInfo.InvariantCulture));
}
}
///
/// Writes a string to the stream.
///
public void WriteString(string? fieldName, string? value)
{
if (fieldName != null && !IncludeDefaultValues && value == null)
{
return;
}
WriteSimpleField(fieldName, value, EscapeOptions.Quotes);
}
///
/// Writes a UTC date/time to the stream.
///
public void WriteDateTime(string? fieldName, DateTimeUtc value)
{
WriteDateTime(fieldName, value, EscapeOptions.None);
}
///
/// Writes a GUID to the stream.
///
public void WriteGuid(string? fieldName, Uuid value)
{
if (fieldName != null && !IncludeDefaultValues && value == Uuid.Empty)
{
return;
}
WriteSimpleField(
fieldName,
value.ToString(),
EscapeOptions.Quotes | EscapeOptions.NoValueEscape);
}
///
public void WriteByteString(string? fieldName, ByteString value)
{
WriteByteString(fieldName, value.ToArray(), 0, value.Length);
}
///
/// Writes a byte string to the stream with a given index and count.
///
/// The byte string length exceeds the maximum allowed.
public void WriteByteString(string? fieldName, byte[] value, int index, int count)
{
if (fieldName != null && !IncludeDefaultValues && value == null)
{
return;
}
if (value == null)
{
WriteSimpleField(fieldName, kNull, EscapeOptions.NoValueEscape);
return;
}
// check the length.
if (Context.MaxByteStringLength > 0 && Context.MaxByteStringLength < count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
WriteSimpleField(
fieldName,
Convert.ToBase64String(value, index, count),
EscapeOptions.Quotes | EscapeOptions.NoValueEscape);
}
#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
///
/// Writes a byte string to the stream.
///
/// The byte string length exceeds the maximum allowed or encoding fails.
public void WriteByteString(string? fieldName, ReadOnlySpan value)
{
// == compares memory reference, comparing to empty means we compare to the default
// If null array is converted to span the span is default
bool isNull = value == ReadOnlySpan.Empty;
if (fieldName != null && !IncludeDefaultValues && isNull)
{
return;
}
if (isNull)
{
WriteSimpleField(fieldName, kNull, EscapeOptions.NoValueEscape);
return;
}
// check the length.
if (Context.MaxByteStringLength > 0 && Context.MaxByteStringLength < value.Length)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
if (value.Length > 0)
{
const int maxStackLimit = 1024;
int length = (value.Length + 2) / 3 * 4;
char[]? arrayPool = null;
Span chars =
length <= maxStackLimit
? stackalloc char[length]
: (arrayPool = ArrayPool.Shared.Rent(length)).AsSpan(0, length);
try
{
bool success = Convert.TryToBase64Chars(
value,
chars,
out int charsWritten,
Base64FormattingOptions.None);
if (success)
{
WriteSimpleFieldAsSpan(
fieldName,
chars[..charsWritten],
EscapeOptions.Quotes | EscapeOptions.NoValueEscape);
return;
}
throw new ServiceResultException(
StatusCodes.BadEncodingError,
"Failed to convert ByteString to Base64");
}
finally
{
if (arrayPool != null)
{
ArrayPool.Shared.Return(arrayPool);
}
}
}
WriteSimpleField(
fieldName,
string.Empty,
EscapeOptions.Quotes | EscapeOptions.NoValueEscape);
}
#endif
///
public void WriteXmlElement(string? fieldName, XmlElement value)
{
if (fieldName != null && !IncludeDefaultValues && value.IsEmpty)
{
return;
}
if (value.IsEmpty)
{
WriteSimpleField(fieldName, kNull, EscapeOptions.NoValueEscape);
return;
}
string? xml = value.OuterXml;
int count = xml!.Length;
if (Context.MaxStringLength > 0 && Context.MaxStringLength < count)
{
throw ServiceResultException.Create(
StatusCodes.BadEncodingLimitsExceeded,
"MaxStringLength {0} < {1}",
Context.MaxStringLength,
count);
}
WriteSimpleField(fieldName, xml, EscapeOptions.Quotes);
}
private void WriteNamespaceIndex(string? fieldName, ushort namespaceIndex)
{
if (namespaceIndex == 0)
{
return;
}
if ((!UseReversibleEncoding || ForceNamespaceUri) &&
namespaceIndex > (ForceNamespaceUriForIndex1 ? 0 : 1))
{
string? uri = Context.NamespaceUris.GetString(namespaceIndex);
if (!string.IsNullOrEmpty(uri))
{
WriteSimpleField(fieldName, uri, EscapeOptions.Quotes);
return;
}
}
if (m_namespaceMappings != null && m_namespaceMappings.Length > namespaceIndex)
{
namespaceIndex = m_namespaceMappings[namespaceIndex];
}
if (namespaceIndex != 0)
{
WriteUInt16(fieldName, namespaceIndex);
}
}
private void WriteNodeIdContents(NodeId value, string? namespaceUri = null)
{
if (value.IdType > IdType.Numeric)
{
WriteInt32("IdType", (int)value.IdType);
}
if (value.TryGetValue(out uint numericId))
{
WriteUInt32("Id", numericId);
}
else if (value.TryGetValue(out string stringId))
{
WriteString("Id", stringId);
}
else if (value.TryGetValue(out Guid guidIdentifier))
{
WriteGuid("Id", guidIdentifier);
}
else if (value.TryGetValue(out ByteString opaqueId))
{
WriteByteString("Id", opaqueId);
}
else
{
throw ServiceResultException.Unexpected(
$"Unexpected Node IdType {value.IdType}");
}
if (namespaceUri != null)
{
WriteString("Namespace", namespaceUri);
}
else
{
WriteNamespaceIndex("Namespace", value.NamespaceIndex);
}
}
///
/// Writes an NodeId to the stream.
///
public void WriteNodeId(string? fieldName, NodeId value)
{
bool isNull = value.IsNull;
if (fieldName != null && isNull && !IncludeDefaultValues)
{
return;
}
if (m_encodeNodeIdAsString)
{
WriteSimpleField(
fieldName,
isNull ? string.Empty : value.Format(Context, ForceNamespaceUri),
EscapeOptions.Quotes);
return;
}
PushStructure(fieldName);
if (!isNull)
{
ushort namespaceIndex = value.NamespaceIndex;
if (ForceNamespaceUri && namespaceIndex > (ForceNamespaceUriForIndex1 ? 0 : 1))
{
string? namespaceUri = Context.NamespaceUris.GetString(namespaceIndex);
WriteNodeIdContents(value, namespaceUri);
}
else
{
WriteNodeIdContents(value);
}
}
PopStructure();
}
///
/// Writes an ExpandedNodeId to the stream.
///
public void WriteExpandedNodeId(string? fieldName, ExpandedNodeId value)
{
bool isNull = value.IsNull;
if (fieldName != null && isNull && !IncludeDefaultValues)
{
return;
}
if (m_encodeNodeIdAsString)
{
WriteSimpleField(
fieldName,
isNull ? string.Empty : value.Format(Context, ForceNamespaceUri),
EscapeOptions.Quotes);
return;
}
PushStructure(fieldName);
if (!isNull)
{
string? namespaceUri = value.NamespaceUri;
ushort namespaceIndex = value.InnerNodeId.NamespaceIndex;
if (ForceNamespaceUri &&
namespaceUri == null &&
namespaceIndex > (ForceNamespaceUriForIndex1 ? 0 : 1))
{
namespaceUri = Context.NamespaceUris.GetString(namespaceIndex);
}
WriteNodeIdContents(value.InnerNodeId, namespaceUri);
uint serverIndex = value.ServerIndex;
if (serverIndex >= 1)
{
if (EncodingToUse == PubSubJsonEncoding.NonReversible)
{
string? uri = Context.ServerUris.GetString(serverIndex);
if (!string.IsNullOrEmpty(uri))
{
WriteSimpleField(
"ServerUri",
uri,
EscapeOptions.Quotes | EscapeOptions.NoFieldNameEscape);
}
PopStructure();
return;
}
if (m_serverMappings != null && m_serverMappings.Length > serverIndex)
{
serverIndex = m_serverMappings[serverIndex];
}
if (serverIndex != 0)
{
WriteUInt32("ServerUri", serverIndex);
}
}
}
PopStructure();
}
///
/// Writes an StatusCode to the stream.
///
public void WriteStatusCode(string? fieldName, StatusCode value)
{
WriteStatusCode(fieldName, value, EscapeOptions.None);
}
///
/// Writes a DiagnosticInfo to the stream.
///
public void WriteDiagnosticInfo(string? fieldName, DiagnosticInfo? value)
{
WriteDiagnosticInfo(fieldName, value, 0);
}
///
/// Writes an QualifiedName to the stream.
///
public void WriteQualifiedName(string? fieldName, QualifiedName value)
{
bool isNull = value.IsNull;
if (fieldName != null && isNull && !IncludeDefaultValues)
{
return;
}
if (m_encodeNodeIdAsString)
{
WriteSimpleField(
fieldName,
isNull ? string.Empty : value.Format(Context, ForceNamespaceUri),
EscapeOptions.Quotes);
return;
}
PushStructure(fieldName);
if (!isNull)
{
WriteString("Name", value.Name);
WriteNamespaceIndex("Uri", value.NamespaceIndex);
}
PopStructure();
}
///
/// Writes an LocalizedText to the stream.
///
public void WriteLocalizedText(string? fieldName, LocalizedText value)
{
bool isNull = value.IsNullOrEmpty;
if (fieldName != null && isNull && !IncludeDefaultValues)
{
return;
}
if (EncodingToUse == PubSubJsonEncoding.NonReversible)
{
WriteSimpleField(
fieldName,
isNull ? string.Empty : value.Text,
EscapeOptions.Quotes);
return;
}
PushStructure(fieldName);
if (!isNull)
{
WriteSimpleField(
"Text",
value.Text,
EscapeOptions.Quotes | EscapeOptions.NoFieldNameEscape);
if (!string.IsNullOrEmpty(value.Locale))
{
WriteSimpleField(
"Locale",
value.Locale,
EscapeOptions.Quotes | EscapeOptions.NoFieldNameEscape);
}
}
PopStructure();
}
///
/// Writes an Variant to the stream.
///
public void WriteVariant(string? fieldName, Variant value)
{
bool isNull =
value.TypeInfo.IsUnknown ||
value.TypeInfo.BuiltInType == BuiltInType.Null ||
value.IsNull;
if (EncodingToUse is PubSubJsonEncoding.Compact or PubSubJsonEncoding.Verbose)
{
if (fieldName != null && isNull && EncodingToUse == PubSubJsonEncoding.Compact)
{
return;
}
PushStructure(fieldName);
if (!isNull)
{
WriteVariantIntoObject("Value", value);
}
PopStructure();
return;
}
if (fieldName != null && isNull && !IncludeDefaultValues)
{
return;
}
CheckAndIncrementNestingLevel();
try
{
if (!isNull && EncodingToUse != PubSubJsonEncoding.NonReversible)
{
PushStructure(fieldName);
// encode enums as int32.
byte encodingByte = (byte)value.TypeInfo.BuiltInType;
if (value.TypeInfo.BuiltInType == BuiltInType.Enumeration)
{
encodingByte = (byte)BuiltInType.Int32;
}
if (!SuppressArtifacts)
{
WriteByte("Type", encodingByte);
}
fieldName = "Body";
}
if (m_commaRequired)
{
m_writer.Write(kComma);
}
if (!string.IsNullOrEmpty(fieldName))
{
EscapeString(fieldName);
m_writer.Write(kQuotationColon);
}
WriteVariantContents(value.Value, value.TypeInfo);
if (!isNull && EncodingToUse != PubSubJsonEncoding.NonReversible)
{
if (value.Value is Matrix matrix)
{
WriteInt32Array("Dimensions", matrix.Dimensions);
}
PopStructure();
}
}
finally
{
m_nestingLevel--;
}
}
private void WriteVariantIntoObject(string? fieldName, Variant value)
{
object? boxed = value.AsBoxedObject();
if (boxed is null)
{
return;
}
try
{
CheckAndIncrementNestingLevel();
bool isNull =
value.TypeInfo.IsUnknown ||
value.TypeInfo.BuiltInType == BuiltInType.Null ||
value.IsNull;
if (!isNull)
{
byte encodingByte = (byte)value.TypeInfo.BuiltInType;
if (value.TypeInfo.BuiltInType == BuiltInType.Enumeration)
{
encodingByte = (byte)BuiltInType.Int32;
}
if (!SuppressArtifacts)
{
WriteByte("UaType", encodingByte);
}
}
if (m_commaRequired)
{
m_writer.Write(kComma);
}
if (!string.IsNullOrEmpty(fieldName))
{
EscapeString(fieldName);
m_writer.Write(kQuotationColon);
m_commaRequired = false;
}
if (value.Value is Matrix matrix)
{
WriteVariantContents(value.Value, value.TypeInfo);
WriteInt32Array("Dimensions", matrix.Dimensions);
return;
}
WriteVariantContents(value.Value, value.TypeInfo);
}
finally
{
m_nestingLevel--;
}
}
///
/// Writes an DataValue array to the stream.
///
public void WriteDataValue(string? fieldName, DataValue value)
{
PushStructure(fieldName);
if (!value.WrappedValue.TypeInfo.IsUnknown &&
value.WrappedValue.TypeInfo.BuiltInType != BuiltInType.Null)
{
if (EncodingToUse is not PubSubJsonEncoding.Compact and not PubSubJsonEncoding.Verbose)
{
WriteVariant("Value", value.WrappedValue);
}
else
{
WriteVariantIntoObject("Value", value.WrappedValue);
}
}
if (value.StatusCode != StatusCodes.Good)
{
WriteStatusCode(
"StatusCode",
value.StatusCode,
EscapeOptions.NoFieldNameEscape);
}
if (value.SourceTimestamp != DateTimeUtc.MinValue)
{
WriteDateTime(
"SourceTimestamp",
value.SourceTimestamp,
EscapeOptions.NoFieldNameEscape);
if (value.SourcePicoseconds != 0)
{
WriteUInt16("SourcePicoseconds", value.SourcePicoseconds);
}
}
if (value.ServerTimestamp != DateTimeUtc.MinValue)
{
WriteDateTime(
"ServerTimestamp",
value.ServerTimestamp,
EscapeOptions.NoFieldNameEscape);
if (value.ServerPicoseconds != 0)
{
WriteUInt16("ServerPicoseconds", value.ServerPicoseconds);
}
}
PopStructure();
}
///
/// Writes an ExtensionObject to the stream.
///
public void WriteExtensionObject(string? fieldName, ExtensionObject value)
{
bool isNull = value.IsNull || value.Encoding == ExtensionObjectEncoding.None;
if (fieldName != null && isNull && !IncludeDefaultValues)
{
return;
}
var encodeable = value.Body as IEncodeable;
if (encodeable != null && EncodingToUse == PubSubJsonEncoding.NonReversible)
{
// non reversible encoding, only the content of the Body field is encoded.
if (value.Body is IStructureTypeInfo structureType &&
structureType.StructureType == StructureType.Union)
{
if (m_commaRequired)
{
m_writer.Write(kComma);
}
if (string.IsNullOrEmpty(fieldName))
{
fieldName = "Value";
}
EscapeString(fieldName);
m_writer.Write(kQuotationColon);
encodeable.Encode(this);
return;
}
PushStructure(fieldName);
encodeable.Encode(this);
PopStructure();
return;
}
PushStructure(fieldName);
ExpandedNodeId typeId = !value.TypeId.IsNull
? value.TypeId
: encodeable?.TypeId ?? NodeId.Null;
var localTypeId = ExpandedNodeId.ToNodeId(typeId, Context.NamespaceUris);
if (EncodingToUse is PubSubJsonEncoding.Compact or PubSubJsonEncoding.Verbose)
{
if (encodeable != null)
{
if (!SuppressArtifacts && !localTypeId.IsNull)
{
WriteNodeId("UaTypeId", localTypeId);
}
encodeable.Encode(this);
}
else if (value.TryGetAsJson(out string? text))
{
if (!SuppressArtifacts && !localTypeId.IsNull)
{
WriteNodeId("UaTypeId", localTypeId);
m_writer.Write(kComma);
}
m_writer.Write(text.Trim()[1..^1]);
}
else if (value.Encoding == ExtensionObjectEncoding.Binary)
{
if (!SuppressArtifacts && !localTypeId.IsNull)
{
WriteNodeId("UaTypeId", localTypeId);
}
WriteByte("UaEncoding", (byte)ExtensionObjectEncoding.Binary);
WriteByteString("UaBody", value.TryGetAsBinary(out ByteString b) ? b : default);
}
else if (value.Encoding == ExtensionObjectEncoding.Xml)
{
if (!SuppressArtifacts && !localTypeId.IsNull)
{
WriteNodeId("UaTypeId", localTypeId);
}
WriteByte("UaEncoding", (byte)ExtensionObjectEncoding.Xml);
WriteXmlElement("UaBody", value.TryGetAsXml(out XmlElement x) ? x : default);
}
PopStructure();
return;
}
WriteNodeId("TypeId", localTypeId);
if (encodeable != null)
{
WriteEncodeable("Body", encodeable, null!);
}
else if (value.TryGetAsJson(out string? text))
{
m_writer.Write(text!.Trim()[1..^1]);
}
else
{
WriteByte("Encoding", (byte)value.Encoding);
if (value.Encoding == ExtensionObjectEncoding.Binary)
{
WriteByteString("Body", value.TryGetAsBinary(out ByteString b) ? b : default);
}
else if (value.Encoding == ExtensionObjectEncoding.Xml)
{
WriteXmlElement("Body", value.TryGetAsXml(out XmlElement x) ? x : default);
}
else if (value.Encoding == ExtensionObjectEncoding.Json)
{
WriteSimpleField("Body", value.TryGetAsJson(out string? j) ? j! : default);
}
}
PopStructure();
}
///
/// Writes an encodeable object to the stream.
///
/// The encoding would create invalid JSON or the nesting level is exceeded.
public void WriteEncodeable(string? fieldName, IEncodeable value, Type systemType)
{
bool isNull = value == null;
if (fieldName != null && isNull && !IncludeDefaultValues)
{
return;
}
if (m_nestingLevel == 0 &&
(m_commaRequired || m_topLevelIsArray) &&
(string.IsNullOrWhiteSpace(fieldName) ^ m_topLevelIsArray))
{
throw ServiceResultException.Create(
StatusCodes.BadEncodingError,
"With Array as top level, encodeables with fieldname will create invalid json");
}
if (m_nestingLevel == 0 &&
!m_commaRequired &&
string.IsNullOrWhiteSpace(fieldName) &&
!m_topLevelIsArray)
{
m_writer.Flush();
if (m_writer.BaseStream.Length == 1) //Opening "{"
{
m_writer.BaseStream.Seek(0, SeekOrigin.Begin);
}
m_dontWriteClosing = true;
}
CheckAndIncrementNestingLevel();
try
{
PushStructure(fieldName);
value?.Encode(this);
PopStructure();
}
finally
{
m_nestingLevel--;
}
}
///
/// Writes an enumerated value to the stream.
///
public void WriteEnumerated(string? fieldName, Enum value)
{
int numeric = Convert.ToInt32(value, CultureInfo.InvariantCulture);
string numericString = numeric.ToString(CultureInfo.InvariantCulture);
if (EncodingToUse is PubSubJsonEncoding.Reversible or PubSubJsonEncoding.Compact)
{
WriteSimpleField(fieldName, numericString);
}
else
{
string valueString = value.ToString();
if (valueString == numericString)
{
WriteSimpleField(fieldName, numericString, EscapeOptions.Quotes);
}
else
{
WriteSimpleField(
fieldName,
Utils.Format("{0}_{1}", valueString!, numeric),
EscapeOptions.Quotes);
}
}
}
///
/// Writes an enumerated EnumValue value to the stream.
///
public void WriteEnumerated(string? fieldName, EnumValue value)
{
int numeric = value.Value;
string numericString = numeric.ToString(CultureInfo.InvariantCulture);
if (EncodingToUse is PubSubJsonEncoding.Reversible or PubSubJsonEncoding.Compact)
{
WriteSimpleField(fieldName, numericString);
}
else
{
string? valueString = value.Symbol;
if (string.IsNullOrEmpty(valueString) || valueString == numericString)
{
WriteSimpleField(fieldName, numericString, EscapeOptions.Quotes);
}
else
{
WriteSimpleField(
fieldName,
Utils.Format("{0}_{1}", valueString!, numeric),
EscapeOptions.Quotes);
}
}
}
///
/// Writes a boolean array to the stream.
///
/// The array length exceeds the maximum allowed.
public void WriteBooleanArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteBoolean(null, values[ii]);
}
PopArray();
}
///
public void WriteSByteArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteSByte(null, values[ii]);
}
PopArray();
}
///
public void WriteByteArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteByte(null, values[ii]);
}
PopArray();
}
///
public void WriteInt16Array(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteInt16(null, values[ii]);
}
PopArray();
}
///
public void WriteUInt16Array(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteUInt16(null, values[ii]);
}
PopArray();
}
///
public void WriteInt32Array(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteInt32(null, values[ii]);
}
PopArray();
}
///
public void WriteUInt32Array(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteUInt32(null, values[ii]);
}
PopArray();
}
///
public void WriteInt64Array(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteInt64(null, values[ii]);
}
PopArray();
}
///
public void WriteUInt64Array(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteUInt64(null, values[ii]);
}
PopArray();
}
///
public void WriteFloatArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteFloat(null, values[ii]);
}
PopArray();
}
///
public void WriteDoubleArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteDouble(null, values[ii]);
}
PopArray();
}
///
public void WriteStringArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteString(null, values[ii]);
}
PopArray();
}
///
public void WriteDateTimeArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
if (values[ii] <= DateTimeUtc.MinValue)
{
WriteSimpleFieldNull(null);
}
else
{
WriteDateTime(null, values[ii]);
}
}
PopArray();
}
///
public void WriteGuidArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteGuid(null, values[ii]);
}
PopArray();
}
///
public void WriteByteStringArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteByteString(null, values[ii]);
}
PopArray();
}
///
public void WriteXmlElementArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteXmlElement(null, values[ii]);
}
PopArray();
}
///
public void WriteNodeIdArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteNodeId(null, values[ii]);
}
PopArray();
}
///
public void WriteExpandedNodeIdArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteExpandedNodeId(null, values[ii]);
}
PopArray();
}
///
public void WriteStatusCodeArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
if (!UseReversibleEncoding && values[ii] == StatusCodes.Good)
{
WriteSimpleFieldNull(null);
}
else
{
WriteStatusCode(null, values[ii]);
}
}
PopArray();
}
///
public void WriteDiagnosticInfoArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteDiagnosticInfo(null, values[ii]);
}
PopArray();
}
///
public void WriteQualifiedNameArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteQualifiedName(null, values[ii]);
}
PopArray();
}
///
public void WriteLocalizedTextArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteLocalizedText(null, values[ii]);
}
PopArray();
}
///
public void WriteVariantArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
if (values[ii] == Variant.Null)
{
WriteSimpleFieldNull(null);
continue;
}
WriteVariant(null, values[ii]);
}
PopArray();
}
///
public void WriteDataValueArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteDataValue(null, values[ii]);
}
PopArray();
}
///
public void WriteExtensionObjectArray(string? fieldName, ArrayOf values)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteExtensionObject(null, values[ii]);
}
PopArray();
}
///
/// Writes an encodeable object array to the stream.
///
/// The array length exceeds the maximum allowed or the encoding would create invalid JSON.
public void WriteEncodeableArray(
string? fieldName,
ArrayOf values,
Type? systemType)
{
if (CheckForSimpleFieldNull(fieldName, values))
{
return;
}
if (string.IsNullOrWhiteSpace(fieldName) && m_nestingLevel == 0 && !m_topLevelIsArray)
{
m_writer.Flush();
if (m_writer.BaseStream.Length == 1) //Opening "{"
{
m_writer.BaseStream.Seek(0, SeekOrigin.Begin);
}
m_nestingLevel++;
PushArray(fieldName);
for (int ii = 0; ii < values.Count; ii++)
{
WriteEncodeable(null, values[ii], systemType!);
}
PopArray();
m_dontWriteClosing = true;
m_nestingLevel--;
}
else if (!string.IsNullOrWhiteSpace(fieldName) &&
m_nestingLevel == 0 &&
m_topLevelIsArray)
{
throw ServiceResultException.Create(
StatusCodes.BadEncodingError,
"With Array as top level, encodeables array with fieldname will create invalid json");
}
else
{
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
for (int ii = 0; ii < values.Count; ii++)
{
WriteEncodeable(null, values[ii], systemType!);
}
PopArray();
}
}
///
/// Writes an enumerated value array to the stream.
///
/// The array length exceeds the maximum allowed or the array element type is invalid.
public void WriteEnumeratedArray(string? fieldName, Array? values, Type? systemType)
{
if (values == null || values.Length == 0)
{
WriteSimpleFieldNull(fieldName);
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Length)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
// encode each element in the array.
Type? arrayType = values.GetType().GetElementType();
if (arrayType!.IsEnum)
{
foreach (Enum value in values)
{
WriteEnumerated(null, value);
}
}
else
{
if (arrayType != typeof(int))
{
throw new ServiceResultException(
StatusCodes.BadEncodingError,
Utils.Format(
"Type '{0}' is not allowed in an Enumeration.",
arrayType.FullName!));
}
foreach (int value in values)
{
WriteEnumerated(null, new EnumValue(value));
}
}
PopArray();
}
///
/// Encode an array according to its valueRank and BuiltInType
///
/// The encoding of the array fails due to invalid data or exceeded limits.
/// The argument is not an array type.
public void WriteArray(
string fieldName,
object array,
int valueRank,
BuiltInType builtInType)
{
// write array.
if (valueRank == ValueRanks.OneDimension)
{
switch (builtInType)
{
case BuiltInType.Boolean:
WriteBooleanArray(fieldName, (bool[])array);
return;
case BuiltInType.SByte:
WriteSByteArray(fieldName, (sbyte[])array);
return;
case BuiltInType.Byte:
WriteByteArray(fieldName, (byte[])array);
return;
case BuiltInType.Int16:
WriteInt16Array(fieldName, (short[])array);
return;
case BuiltInType.UInt16:
WriteUInt16Array(fieldName, (ushort[])array);
return;
case BuiltInType.Int32:
WriteInt32Array(fieldName, (int[])array);
return;
case BuiltInType.UInt32:
WriteUInt32Array(fieldName, (uint[])array);
return;
case BuiltInType.Int64:
WriteInt64Array(fieldName, (long[])array);
return;
case BuiltInType.UInt64:
WriteUInt64Array(fieldName, (ulong[])array);
return;
case BuiltInType.Float:
WriteFloatArray(fieldName, (float[])array);
return;
case BuiltInType.Double:
WriteDoubleArray(fieldName, (double[])array);
return;
case BuiltInType.String:
WriteStringArray(fieldName, (string[])array);
return;
case BuiltInType.DateTime:
WriteDateTimeArray(fieldName, (DateTimeUtc[])array);
return;
case BuiltInType.Guid:
WriteGuidArray(fieldName, (Uuid[])array);
return;
case BuiltInType.ByteString:
WriteByteStringArray(fieldName, (ByteString[])array);
return;
case BuiltInType.XmlElement:
WriteXmlElementArray(fieldName, (XmlElement[])array);
return;
case BuiltInType.NodeId:
WriteNodeIdArray(fieldName, (NodeId[])array);
return;
case BuiltInType.ExpandedNodeId:
WriteExpandedNodeIdArray(fieldName, (ExpandedNodeId[])array);
return;
case BuiltInType.StatusCode:
WriteStatusCodeArray(fieldName, (StatusCode[])array);
return;
case BuiltInType.QualifiedName:
WriteQualifiedNameArray(fieldName, (QualifiedName[])array);
return;
case BuiltInType.LocalizedText:
WriteLocalizedTextArray(fieldName, (LocalizedText[])array);
return;
case BuiltInType.ExtensionObject:
WriteExtensionObjectArray(fieldName, (ExtensionObject[])array);
return;
case BuiltInType.DataValue:
WriteDataValueArray(fieldName, (DataValue[])array);
return;
case BuiltInType.DiagnosticInfo:
WriteDiagnosticInfoArray(fieldName, (DiagnosticInfo[])array);
return;
case BuiltInType.Enumeration:
if (array is null or Array)
{
WriteEnumeratedArray(
fieldName,
(Array?)array,
array?.GetType().GetElementType());
return;
}
throw ServiceResultException.Create(
StatusCodes.BadEncodingError,
"Unexpected non Array type encountered while encoding an array of enumeration: {0}",
array.GetType());
case BuiltInType.Variant:
if (array is null or Variant[])
{
WriteVariantArray(fieldName, (Variant[])array!);
return;
}
// try to write IEncodeable Array
if (array is IEncodeable[] encodeableArray)
{
WriteEncodeableArray(
fieldName,
encodeableArray,
array.GetType().GetElementType()
?? throw new InvalidOperationException("Argument is not an array type."));
return;
}
if (array is object[] objects)
{
WriteObjectArray(fieldName, objects);
return;
}
throw ServiceResultException.Create(
StatusCodes.BadEncodingError,
"Unexpected type encountered while encoding an array of Variants: {0}",
array.GetType());
case BuiltInType.Null:
case BuiltInType.Number:
case BuiltInType.Integer:
case BuiltInType.UInteger:
// try to write IEncodeable Array
if (array is null or IEncodeable[])
{
WriteEncodeableArray(
fieldName,
(IEncodeable[])array!,
array?.GetType().GetElementType()
?? throw new InvalidOperationException("Argument is not an array type."));
return;
}
throw ServiceResultException.Create(
StatusCodes.BadEncodingError,
"Unexpected BuiltInType encountered while encoding an array: {0}",
builtInType);
default:
throw ServiceResultException.Unexpected(
$"Unexpected BuiltInType {builtInType}");
}
}
// write matrix.
else if (valueRank > ValueRanks.OneDimension)
{
if (array is not Matrix matrix)
{
if (array is Array multiArray && multiArray.Rank == valueRank)
{
matrix = new Matrix(multiArray, builtInType);
}
else
{
throw ServiceResultException.Create(
StatusCodes.BadEncodingError,
"Unexpected array type encountered while encoding array: {0}",
array.GetType().Name);
}
}
if (EncodingToUse is PubSubJsonEncoding.Compact or PubSubJsonEncoding.Verbose)
{
WriteArrayDimensionMatrix(fieldName, builtInType, matrix);
}
else
{
int index = 0;
WriteStructureMatrix(fieldName, matrix, 0, ref index, matrix.TypeInfo);
}
return;
// field is omitted
}
}
///
/// Writes a raw value.
///
public void WriteRawValue(FieldMetaData field, DataValue dv, DataSetFieldContentMask mask)
{
m_nestingLevel++;
try
{
if (m_commaRequired)
{
m_writer.Write(kComma);
}
EscapeString(field.Name);
m_writer.Write(kQuotationColon);
m_commaRequired = false;
bool dimensionsInline = false;
if (mask is not DataSetFieldContentMask.None and not DataSetFieldContentMask.RawData)
{
m_writer.Write(kLeftCurlyBrace);
m_writer.Write(kQuotation);
m_writer.Write("Value");
m_writer.Write(kQuotationColon);
dimensionsInline = true;
}
if (mask == DataSetFieldContentMask.None && StatusCode.IsBad(dv.StatusCode))
{
dv = new DataValue(new Variant(dv.StatusCode));
}
WriteRawValueContents(field, dv, dimensionsInline);
if (mask is not DataSetFieldContentMask.None and not DataSetFieldContentMask.RawData)
{
if ((mask & DataSetFieldContentMask.StatusCode) != 0 &&
dv.StatusCode != StatusCodes.Good)
{
WriteStatusCode(nameof(dv.StatusCode), dv.StatusCode);
}
if ((mask & DataSetFieldContentMask.SourceTimestamp) != 0 &&
dv.SourceTimestamp != DateTimeUtc.MinValue)
{
WriteDateTime(nameof(dv.SourceTimestamp), dv.SourceTimestamp);
if (dv.SourcePicoseconds != 0)
{
WriteUInt16(nameof(dv.SourcePicoseconds), dv.SourcePicoseconds);
}
}
if ((mask & DataSetFieldContentMask.ServerTimestamp) != 0 &&
dv.ServerTimestamp != DateTimeUtc.MinValue)
{
WriteDateTime(nameof(dv.ServerTimestamp), dv.ServerTimestamp);
if (dv.ServerPicoseconds != 0)
{
WriteUInt16(nameof(dv.ServerPicoseconds), dv.ServerPicoseconds);
}
}
m_writer.Write(kRightCurlyBrace);
}
m_commaRequired = true;
}
finally
{
m_nestingLevel--;
}
}
///
public void WriteEncodeable(string? fieldName, T value) where T : IEncodeable, new()
{
WriteEncodeable(fieldName, value, typeof(T));
}
///
public void WriteEncodeable(string? fieldName, T value, ExpandedNodeId encodeableTypeId) where T : IEncodeable
{
WriteEncodeable(fieldName, value, typeof(T));
}
///
public void WriteEncodeableAsExtensionObject(string? fieldName, T value) where T : IEncodeable
{
WriteExtensionObject(fieldName, new ExtensionObject(value));
}
///
public void WriteEnumerated(string? fieldName, T value) where T : struct, Enum
{
WriteEnumerated(fieldName, (Enum)value);
}
///
public void WriteEncodeableArray(string? fieldName, ArrayOf values) where T : IEncodeable, new()
{
WriteEncodeableArray(fieldName, values.ConvertAll(d => (IEncodeable)d), typeof(T));
}
///
public void WriteEncodeableArray(string? fieldName, ArrayOf values, ExpandedNodeId encodeableTypeId) where T : IEncodeable
{
WriteEncodeableArray(fieldName, values.ConvertAll(d => (IEncodeable)d), typeof(T));
}
///
public void WriteEncodeableArrayAsExtensionObjects(string? fieldName, ArrayOf values) where T : IEncodeable
{
WriteExtensionObjectArray(fieldName, values.ConvertAll(d => new ExtensionObject(d)));
}
///
public void WriteEnumeratedArray(string? fieldName, ArrayOf values) where T : struct, Enum
{
WriteEnumeratedArray(fieldName, values.ToArray(), typeof(T));
}
///
public void WriteEnumeratedArray(string? fieldName, ArrayOf values)
{
if (values.IsEmpty)
{
WriteSimpleFieldNull(fieldName);
return;
}
PushArray(fieldName);
// check the length.
if (Context.MaxArrayLength > 0 && Context.MaxArrayLength < values.Count)
{
throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded);
}
// encode each element in the array.
foreach (EnumValue value in values)
{
WriteEnumerated(null, value);
}
PopArray();
}
///
public void WriteVariantValue(string? fieldName, Variant value)
{
}
///
public void WriteEncodeableMatrix(string? fieldName, MatrixOf values) where T : IEncodeable, new()
{
}
///
public void WriteEncodeableMatrix(string? fieldName, MatrixOf values, ExpandedNodeId encodeableTypeId) where T : IEncodeable
{
}
private void WriteRawExtensionObject(object value)
{
if (value is ExtensionObject eo)
{
value = eo.Body!;
}
if (value is IEncodeable encodeable)
{
PushStructure(null);
encodeable.Encode(this);
PopStructure();
}
else
{
if (m_commaRequired)
{
m_writer.Write(kComma);
}
m_writer.Write(kNull);
}
m_commaRequired = true;
}
private void WriteRawVariantArray(object value)
{
if (value is IList list)
{
PushArray(null);
foreach (Variant ii in list)
{
if (!ii.IsNull)
{
Variant vt = ii;
PushStructure(null);
WriteVariantContents(vt.Value, vt.TypeInfo);
PopStructure();
}
else
{
if (m_commaRequired)
{
m_writer.Write(kComma);
}
m_writer.Write(kNull);
}
}
PopArray();
}
else
{
m_writer.Write(kNull);
}
m_commaRequired = true;
}
private void WriteRawValueContents(FieldMetaData field, DataValue dv, bool dimensionsInline)
{
object? value = dv.WrappedValue.AsBoxedObject(Variant.BoxingBehavior.LegacyWithMatrix);
TypeInfo typeInfo = dv.WrappedValue.TypeInfo;
if (dv.WrappedValue == Variant.Null)
{
value = TypeInfo.GetDefaultValue((BuiltInType)field.BuiltInType, field.ValueRank);
typeInfo = new TypeInfo((BuiltInType)field.BuiltInType, field.ValueRank);
if (value != null)
{
WriteVariantContents(value, typeInfo);
}
else if (field.ValueRank >= 0)
{
m_writer.Write(kLeftSquareBracket);
m_writer.Write(kRightSquareBracket);
}
else if (field.BuiltInType == (byte)BuiltInType.ExtensionObject)
{
m_writer.Write(kLeftCurlyBrace);
m_writer.Write(kRightCurlyBrace);
}
else
{
m_writer.Write(kNull);
}
m_commaRequired = true;
return;
}
if (field.ValueRank == ValueRanks.Scalar)
{
if (field.BuiltInType == (byte)BuiltInType.ExtensionObject)
{
WriteRawExtensionObject(value!);
return;
}
}
else
{
if (value is Matrix matrix)
{
if (!dimensionsInline)
{
PushStructure(null);
}
PushArray(!dimensionsInline ? "Array" : null);
foreach (object ii in matrix.Elements)
{
if (m_commaRequired)
{
m_writer.Write(kComma);
}
if (field.BuiltInType == (byte)BuiltInType.ExtensionObject)
{
m_commaRequired = false;
WriteRawExtensionObject(ii);
m_commaRequired = true;
continue;
}
else if (field.BuiltInType == (byte)BuiltInType.Variant)
{
m_commaRequired = false;
if (ii is Variant vt)
{
WriteVariant(null, vt);
}
else
{
m_writer.Write(kNull);
}
m_commaRequired = true;
continue;
}
WriteVariantContents(
ii,
new TypeInfo((BuiltInType)field.BuiltInType, ValueRanks.Scalar));
m_commaRequired = true;
}
PopArray();
WriteInt32Array("Dimensions", matrix.Dimensions);
if (!dimensionsInline)
{
PopStructure();
}
m_commaRequired = true;
return;
}
if (field.BuiltInType == (byte)BuiltInType.ExtensionObject &&
value is IList list)
{
PushArray(null);
foreach (ExtensionObject element in list)
{
WriteRawExtensionObject(element);
}
PopArray();
m_commaRequired = true;
return;
}
if (field.BuiltInType == (byte)BuiltInType.Variant && value is IList)
{
WriteRawVariantArray(value);
return;
}
}
WriteVariantContents(value, typeInfo);
if (EncodingToUse == PubSubJsonEncoding.Reversible)
{
if (dv.WrappedValue.AsBoxedObject(Variant.BoxingBehavior.LegacyWithMatrix) is Matrix matrix)
{
WriteInt32Array("Dimensions", matrix.Dimensions);
}
m_writer.Write(kRightCurlyBrace);
}
}
///
/// Writes the contents of a Variant to the stream.
///
/// An unexpected built-in type is encountered.
public void WriteVariantContents(object? value, TypeInfo typeInfo)
{
bool inVariantWithEncoding = m_inVariantWithEncoding;
try
{
m_inVariantWithEncoding = true;
// check for null.
if (value == null)
{
return;
}
m_commaRequired = false;
// write scalar.
if (typeInfo.ValueRank < 0)
{
switch (typeInfo.BuiltInType)
{
case BuiltInType.Boolean:
WriteBoolean(null, (bool)value);
return;
case BuiltInType.SByte:
WriteSByte(null, (sbyte)value);
return;
case BuiltInType.Byte:
WriteByte(null, (byte)value);
return;
case BuiltInType.Int16:
WriteInt16(null, (short)value);
return;
case BuiltInType.UInt16:
WriteUInt16(null, (ushort)value);
return;
case BuiltInType.Int32:
WriteInt32(null, (int)value);
return;
case BuiltInType.UInt32:
WriteUInt32(null, (uint)value);
return;
case BuiltInType.Int64:
WriteInt64(null, (long)value);
return;
case BuiltInType.UInt64:
WriteUInt64(null, (ulong)value);
return;
case BuiltInType.Float:
WriteFloat(null, (float)value);
return;
case BuiltInType.Double:
WriteDouble(null, (double)value);
return;
case BuiltInType.String:
WriteString(null, (string)value);
return;
case BuiltInType.DateTime:
WriteDateTime(null, (DateTimeUtc)value);
return;
case BuiltInType.Guid:
WriteGuid(null, (Uuid)value);
return;
case BuiltInType.ByteString:
WriteByteString(null, (ByteString)value);
return;
case BuiltInType.XmlElement:
WriteXmlElement(null, (XmlElement)value);
return;
case BuiltInType.NodeId:
WriteNodeId(null, (NodeId)value);
return;
case BuiltInType.ExpandedNodeId:
WriteExpandedNodeId(null, (ExpandedNodeId)value);
return;
case BuiltInType.StatusCode:
WriteStatusCode(null, (StatusCode)value);
return;
case BuiltInType.QualifiedName:
WriteQualifiedName(null, (QualifiedName)value);
return;
case BuiltInType.LocalizedText:
WriteLocalizedText(null, (LocalizedText)value);
return;
case BuiltInType.ExtensionObject:
WriteExtensionObject(null, (ExtensionObject)value);
return;
case BuiltInType.DataValue:
WriteDataValue(null, (DataValue)value);
return;
case BuiltInType.Enumeration:
WriteEnumerated(null, (Enum)value);
return;
case BuiltInType.DiagnosticInfo:
WriteDiagnosticInfo(null, (DiagnosticInfo)value);
return;
case BuiltInType.Null:
case BuiltInType.Variant:
case BuiltInType.Number:
case BuiltInType.Integer:
case BuiltInType.UInteger:
// Should this not throw?
break;
default:
throw ServiceResultException.Unexpected(
$"Unexpected BuiltInType {typeInfo.BuiltInType}");
}
}
// write array
else if (typeInfo.ValueRank >= ValueRanks.OneDimension)
{
int valueRank = typeInfo.ValueRank;
if (EncodingToUse != PubSubJsonEncoding.NonReversible && value is Matrix matrix)
{
// linearize the matrix
value = matrix.Elements;
valueRank = ValueRanks.OneDimension;
}
WriteArray(null!, value, valueRank, typeInfo.BuiltInType);
}
}
finally
{
m_inVariantWithEncoding = inVariantWithEncoding;
}
}
///
/// Writes a Variant array to the stream.
///
/// The array length exceeds the maximum allowed.
public void WriteObjectArray(string? fieldName, ArrayOf