diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Microsoft.Azure.Cosmos.Encryption.Custom.csproj b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Microsoft.Azure.Cosmos.Encryption.Custom.csproj
index 6f492824b6..cca4d719bf 100644
--- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Microsoft.Azure.Cosmos.Encryption.Custom.csproj
+++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Microsoft.Azure.Cosmos.Encryption.Custom.csproj
@@ -38,11 +38,11 @@
-
+
-
+
diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/JObjectSqlSerializer.Preview.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/JObjectSqlSerializer.Preview.cs
index e5edc3ade2..aadbe485f3 100644
--- a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/JObjectSqlSerializer.Preview.cs
+++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/JObjectSqlSerializer.Preview.cs
@@ -49,10 +49,10 @@ internal virtual (TypeMarker typeMarker, byte[] serializedBytes, int serializedB
(buffer, length) = SerializeString(propertyValue.ToObject());
return (TypeMarker.String, buffer, length);
case JTokenType.Array:
- (buffer, length) = SerializeString(propertyValue.ToString());
+ (buffer, length) = SerializeString(propertyValue.ToString(Formatting.None));
return (TypeMarker.Array, buffer, length);
case JTokenType.Object:
- (buffer, length) = SerializeString(propertyValue.ToString());
+ (buffer, length) = SerializeString(propertyValue.ToString(Formatting.None));
return (TypeMarker.Object, buffer, length);
default:
throw new InvalidOperationException($" Invalid or Unsupported Data Type Passed : {propertyValue.Type}");
diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/JsonNodeSqlSerializer.Preview.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/JsonNodeSqlSerializer.Preview.cs
new file mode 100644
index 0000000000..6a607a3730
--- /dev/null
+++ b/Microsoft.Azure.Cosmos.Encryption.Custom/src/Transformation/JsonNodeSqlSerializer.Preview.cs
@@ -0,0 +1,93 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// ------------------------------------------------------------
+
+#if ENCRYPTION_CUSTOM_PREVIEW && NET8_0_OR_GREATER
+namespace Microsoft.Azure.Cosmos.Encryption.Custom.Transformation
+{
+ using System;
+ using System.Diagnostics;
+ using System.Text.Json;
+ using System.Text.Json.Nodes;
+ using Microsoft.Data.Encryption.Cryptography.Serializers;
+
+ internal class JsonNodeSqlSerializer
+ {
+ private static readonly SqlBitSerializer SqlBoolSerializer = new ();
+ private static readonly SqlFloatSerializer SqlDoubleSerializer = new ();
+ private static readonly SqlBigIntSerializer SqlLongSerializer = new ();
+
+ // UTF-8 encoding.
+ private static readonly SqlVarCharSerializer SqlVarCharSerializer = new (size: -1, codePageCharacterEncoding: 65001);
+
+#pragma warning disable SA1101 // Prefix local calls with this - false positive on SerializeFixed
+ internal virtual (TypeMarker typeMarker, byte[] serializedBytes, int serializedBytesCount) Serialize(JsonNode propertyValue, ArrayPoolManager arrayPoolManager)
+ {
+ byte[] buffer;
+ int length;
+
+ if (propertyValue == null)
+ {
+ return (TypeMarker.Null, null, -1);
+ }
+
+ switch (propertyValue.GetValueKind())
+ {
+ case JsonValueKind.Undefined:
+ Debug.Assert(false, "Undefined value cannot be in the JSON");
+ return (default, null, -1);
+ case JsonValueKind.Null:
+ Debug.Assert(false, "Null type should have been handled by caller");
+ return (TypeMarker.Null, null, -1);
+ case JsonValueKind.True:
+ (buffer, length) = SerializeFixed(SqlBoolSerializer, true);
+ return (TypeMarker.Boolean, buffer, length);
+ case JsonValueKind.False:
+ (buffer, length) = SerializeFixed(SqlBoolSerializer, false);
+ return (TypeMarker.Boolean, buffer, length);
+ case JsonValueKind.Number:
+ if (long.TryParse(propertyValue.ToJsonString(), out long longValue))
+ {
+ (buffer, length) = SerializeFixed(SqlLongSerializer, longValue);
+ return (TypeMarker.Long, buffer, length);
+ }
+ else if (double.TryParse(propertyValue.ToJsonString(), out double doubleValue))
+ {
+ (buffer, length) = SerializeFixed(SqlDoubleSerializer, doubleValue);
+ return (TypeMarker.Double, buffer, length);
+ }
+ else
+ {
+ throw new InvalidOperationException("Unsupported Number type");
+ }
+
+ case JsonValueKind.String:
+ (buffer, length) = SerializeString(propertyValue.GetValue());
+ return (TypeMarker.String, buffer, length);
+ case JsonValueKind.Array:
+ (buffer, length) = SerializeString(propertyValue.ToJsonString());
+ return (TypeMarker.Array, buffer, length);
+ case JsonValueKind.Object:
+ (buffer, length) = SerializeString(propertyValue.ToJsonString());
+ return (TypeMarker.Object, buffer, length);
+ default:
+ throw new InvalidOperationException($" Invalid or Unsupported Data Type Passed : {propertyValue.GetValueKind()}");
+ }
+
+ (byte[], int) SerializeFixed(IFixedSizeSerializer serializer, T value)
+ {
+ byte[] buffer = arrayPoolManager.Rent(serializer.GetSerializedMaxByteCount());
+ int length = serializer.Serialize(value, buffer);
+ return (buffer, length);
+ }
+
+ (byte[], int) SerializeString(string value)
+ {
+ byte[] buffer = arrayPoolManager.Rent(SqlVarCharSerializer.GetSerializedMaxByteCount(value.Length));
+ int length = SqlVarCharSerializer.Serialize(value, buffer);
+ return (buffer, length);
+ }
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests.csproj b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests.csproj
index 7e1f7d48fe..8dbf3f22fc 100644
--- a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests.csproj
+++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests.csproj
@@ -9,6 +9,7 @@
false
Microsoft.Azure.Cosmos.Encryption.Tests
$(LangVersion)
+ $(DefineConstants);ENCRYPTION_CUSTOM_PREVIEW
diff --git a/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/JsonNodeSqlSerializerTests.cs b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/JsonNodeSqlSerializerTests.cs
new file mode 100644
index 0000000000..deb6bb7b35
--- /dev/null
+++ b/Microsoft.Azure.Cosmos.Encryption.Custom/tests/Microsoft.Azure.Cosmos.Encryption.Custom.Tests/Transformation/JsonNodeSqlSerializerTests.cs
@@ -0,0 +1,92 @@
+#if ENCRYPTION_CUSTOM_PREVIEW && NET8_0_OR_GREATER
+
+namespace Microsoft.Azure.Cosmos.Encryption.Tests.Transformation
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text.Json.Nodes;
+ using Microsoft.Azure.Cosmos.Encryption.Custom;
+ using Microsoft.Azure.Cosmos.Encryption.Custom.Transformation;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using Newtonsoft.Json.Linq;
+
+ [TestClass]
+ public class JsonNodeSqlSerializerTests
+ {
+ private static ArrayPoolManager _poolManager;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext context)
+ {
+ _ = context;
+ _poolManager = new ArrayPoolManager();
+ }
+
+ [TestMethod]
+ [DynamicData(nameof(SerializationSamples))]
+ public void Serialize_SupportedValue(JsonNode testNode, byte expectedType, byte[] expectedBytes, int expectedLength)
+ {
+ JsonNodeSqlSerializer serializer = new();
+
+ (TypeMarker serializedType, byte[] serializedBytes, int serializedBytesCount) = serializer.Serialize(testNode, _poolManager);
+
+ Assert.AreEqual((TypeMarker)expectedType, serializedType);
+ Assert.AreEqual(expectedLength, serializedBytesCount);
+ if (expectedLength == -1)
+ {
+ Assert.IsTrue(serializedBytes == null);
+ }
+ else
+ {
+ Assert.IsTrue(expectedBytes.SequenceEqual(serializedBytes.AsSpan(0, serializedBytesCount).ToArray()));
+ }
+ }
+
+ public static IEnumerable