From dd71cb5d968c9587015f6879ef165fbd4c21836d Mon Sep 17 00:00:00 2001 From: Alekhya Polavarapu Date: Thu, 30 Oct 2025 14:09:49 -0700 Subject: [PATCH 01/12] Fix the Dollar prefixed columns during serialization --- .../Converters/DatabaseObjectConverter.cs | 83 ++++++++++++-- .../SerializationDeserializationTests.cs | 101 ++++++++++++++++-- 2 files changed, 172 insertions(+), 12 deletions(-) diff --git a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs index c6e694a394..cbfbea5f6b 100644 --- a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs +++ b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs @@ -17,6 +17,8 @@ namespace Azure.DataApiBuilder.Core.Services.MetadataProviders.Converters public class DatabaseObjectConverter : JsonConverter { private const string TYPE_NAME = "TypeName"; + private const string DOLLAR_CHAR = "$"; + private const string ESCAPED_DOLLARCHAR = "_$"; public override DatabaseObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { @@ -29,6 +31,18 @@ public override DatabaseObject Read(ref Utf8JsonReader reader, Type typeToConver DatabaseObject objA = (DatabaseObject)JsonSerializer.Deserialize(document, concreteType, options)!; + foreach (PropertyInfo prop in objA.GetType().GetProperties()) + { + if (IsSourceDefinitionProperty(prop)) + { + SourceDefinition? sourceDef = (SourceDefinition?)prop.GetValue(objA); + if (sourceDef is not null) + { + UnescapeDollaredColumns(sourceDef); + } + } + } + return objA; } } @@ -42,28 +56,85 @@ public override void Write(Utf8JsonWriter writer, DatabaseObject value, JsonSeri writer.WriteStartObject(); - // Add TypeName property in DatabaseObject object that we are serializing based on its type. (DatabaseTable, DatabaseView) - // We add this property to differentiate between them in the dictionary. This extra property gets used in deserialization above. - // for example if object is DatabaseTable then we need to add - // "TypeName": "Azure.DataApiBuilder.Config.DatabasePrimitives.DatabaseTable, Azure.DataApiBuilder.Config", + // Add TypeName property in DatabaseObject object that we are serializing based on its type. writer.WriteString(TYPE_NAME, GetTypeNameFromType(value.GetType())); // Add other properties of DatabaseObject foreach (PropertyInfo prop in value.GetType().GetProperties()) { - // Skip the TypeName property, as it has been handled above if (prop.Name == TYPE_NAME) { continue; } writer.WritePropertyName(prop.Name); - JsonSerializer.Serialize(writer, prop.GetValue(value), options); + object? propVal = prop.GetValue(value); + Type propType = prop.PropertyType; + + // enforcing that we only escape columns for properties whose type is exactly SourceDefinition(DatabaseTables) + if (IsSourceDefinitionProperty(prop) && propVal is SourceDefinition sourceDef && propVal.GetType() == typeof(SourceDefinition)) + { + EscapeDollaredColumns(sourceDef); + } + + JsonSerializer.Serialize(writer, propVal, propType, options); } writer.WriteEndObject(); } + private static bool IsSourceDefinitionProperty(PropertyInfo prop) + { + // Only return true for properties whose type is exactly SourceDefinition (not subclasses) + return prop.PropertyType == typeof(SourceDefinition); + } + + /// + /// Escapes column keys that start with '$' to '_$' for serialization. + /// + private static void EscapeDollaredColumns(SourceDefinition sourceDef) + { + if (sourceDef.Columns is null || sourceDef.Columns.Count == 0) + { + return; + } + + List keysToEscape = sourceDef.Columns.Keys + .Where(k => k.StartsWith(DOLLAR_CHAR, StringComparison.Ordinal)) + .ToList(); + + foreach (string key in keysToEscape) + { + ColumnDefinition col = sourceDef.Columns[key]; + sourceDef.Columns.Remove(key); + string newKey = ESCAPED_DOLLARCHAR + key[1..]; + sourceDef.Columns[newKey] = col; + } + } + + /// + /// Unescapes column keys that start with '_$' to '$' for deserialization. + /// + private static void UnescapeDollaredColumns(SourceDefinition sourceDef) + { + if (sourceDef.Columns is null || sourceDef.Columns.Count == 0) + { + return; + } + + List keysToUnescape = sourceDef.Columns.Keys + .Where(k => k.StartsWith(ESCAPED_DOLLARCHAR, StringComparison.Ordinal)) + .ToList(); + + foreach (string key in keysToUnescape) + { + ColumnDefinition col = sourceDef.Columns[key]; + sourceDef.Columns.Remove(key); + string newKey = DOLLAR_CHAR + key[2..]; + sourceDef.Columns[newKey] = col; + } + } + private static Type GetTypeFromName(string typeName) { Type? type = Type.GetType(typeName); diff --git a/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs b/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs index 44978cd6aa..fe4c5ec585 100644 --- a/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs +++ b/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs @@ -276,8 +276,96 @@ public void TestDictionaryDatabaseObjectSerializationDeserialization() VerifySourceDefinitionSerializationDeserialization(deserializedDatabaseTable.TableDefinition, _databaseTable.TableDefinition, "FirstName"); } - private void InitializeObjects() + /// + /// Validates serialization and deserilization of Dictionary containing DatabaseTable + /// The table will have dollar sign prefix ($) in the column name + /// this is how we serialize and deserialize metadataprovider.EntityToDatabaseObject dict. + /// + [TestMethod] + public void TestDictionaryDatabaseObjectSerializationDeserialization_WithDollarColumn() { + InitializeObjects(true); + + _options = new() + { + Converters = { + new DatabaseObjectConverter(), + new TypeConverter() + }, + ReferenceHandler = ReferenceHandler.Preserve, + }; + + Dictionary dict = new() { { "person", _databaseTable } }; + + string serializedDict = JsonSerializer.Serialize(dict, _options); + Dictionary deserializedDict = JsonSerializer.Deserialize>(serializedDict, _options)!; + + DatabaseTable deserializedDatabaseTable = (DatabaseTable)deserializedDict["person"]; + + Assert.AreEqual(deserializedDatabaseTable.SourceType, _databaseTable.SourceType); + Assert.AreEqual(deserializedDatabaseTable.FullName, _databaseTable.FullName); + deserializedDatabaseTable.Equals(_databaseTable); + VerifySourceDefinitionSerializationDeserialization(deserializedDatabaseTable.SourceDefinition, _databaseTable.SourceDefinition, "$FirstName"); + VerifySourceDefinitionSerializationDeserialization(deserializedDatabaseTable.TableDefinition, _databaseTable.TableDefinition, "$FirstName"); + } + + /// + /// Validates serialization and deserilization of Dictionary containing DatabaseView + /// The table will have dollar sign prefix ($) in the column name + /// this is how we serialize and deserialize metadataprovider.EntityToDatabaseObject dict. + /// + [TestMethod] + public void TestDatabaseViewSerializationDeserialization_WithDollarColumn() + { + InitializeObjects(true); + + TestTypeNameChanges(_databaseView, "DatabaseView"); + + // Test to catch if there is change in number of properties/fields + // Note: On Addition of property make sure it is added in following object creation _databaseView and include in serialization + // and deserialization test. + int fields = typeof(DatabaseView).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Length; + Assert.AreEqual(fields, 6); + + string serializedDatabaseView = JsonSerializer.Serialize(_databaseView, _options); + DatabaseView deserializedDatabaseView = JsonSerializer.Deserialize(serializedDatabaseView, _options)!; + + Assert.AreEqual(deserializedDatabaseView.SourceType, _databaseView.SourceType); + deserializedDatabaseView.Equals(_databaseView); + VerifySourceDefinitionSerializationDeserialization(deserializedDatabaseView.SourceDefinition, _databaseView.SourceDefinition, "$FirstName"); + VerifySourceDefinitionSerializationDeserialization(deserializedDatabaseView.ViewDefinition, _databaseView.ViewDefinition, "$FirstName"); + } + + /// + /// Validates serialization and deserilization of Dictionary containing DatabaseStoredProcedure + /// The table will have dollar sign prefix ($) in the column name + /// this is how we serialize and deserialize metadataprovider.EntityToDatabaseObject dict. + /// + [TestMethod] + public void TestDatabaseStoredProcedureSerializationDeserialization_WithDollarColumn() + { + InitializeObjects(true); + + TestTypeNameChanges(_databaseStoredProcedure, "DatabaseStoredProcedure"); + + // Test to catch if there is change in number of properties/fields + // Note: On Addition of property make sure it is added in following object creation _databaseStoredProcedure and include in serialization + // and deserialization test. + int fields = typeof(DatabaseStoredProcedure).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Length; + Assert.AreEqual(fields, 6); + + string serializedDatabaseSP = JsonSerializer.Serialize(_databaseStoredProcedure, _options); + DatabaseStoredProcedure deserializedDatabaseSP = JsonSerializer.Deserialize(serializedDatabaseSP, _options)!; + + Assert.AreEqual(deserializedDatabaseSP.SourceType, _databaseStoredProcedure.SourceType); + deserializedDatabaseSP.Equals(_databaseStoredProcedure); + VerifySourceDefinitionSerializationDeserialization(deserializedDatabaseSP.SourceDefinition, _databaseStoredProcedure.SourceDefinition, "$FirstName", true); + VerifySourceDefinitionSerializationDeserialization(deserializedDatabaseSP.StoredProcedureDefinition, _databaseStoredProcedure.StoredProcedureDefinition, "$FirstName", true); + } + + private void InitializeObjects(bool generateDollaredColumn = false) + { + string columnName = generateDollaredColumn ? "$FirstName" : "FirstName"; _options = new() { // ObjectConverter behavior different in .NET8 most likely due to @@ -289,10 +377,11 @@ private void InitializeObjects() new DatabaseObjectConverter(), new TypeConverter() } + }; _columnDefinition = GetColumnDefinition(typeof(string), DbType.String, true, false, false, new string("John"), false); - _sourceDefinition = GetSourceDefinition(false, false, new List() { "FirstName" }, _columnDefinition); + _sourceDefinition = GetSourceDefinition(false, false, new List() { columnName }, _columnDefinition); _databaseTable = new DatabaseTable() { @@ -311,10 +400,10 @@ private void InitializeObjects() { IsInsertDMLTriggerEnabled = false, IsUpdateDMLTriggerEnabled = false, - PrimaryKey = new List() { "FirstName" }, + PrimaryKey = new List() { columnName }, }, }; - _databaseView.ViewDefinition.Columns.Add("FirstName", _columnDefinition); + _databaseView.ViewDefinition.Columns.Add(columnName, _columnDefinition); _parameterDefinition = new() { @@ -331,10 +420,10 @@ private void InitializeObjects() SourceType = EntitySourceType.StoredProcedure, StoredProcedureDefinition = new() { - PrimaryKey = new List() { "FirstName" }, + PrimaryKey = new List() { columnName }, } }; - _databaseStoredProcedure.StoredProcedureDefinition.Columns.Add("FirstName", _columnDefinition); + _databaseStoredProcedure.StoredProcedureDefinition.Columns.Add(columnName, _columnDefinition); _databaseStoredProcedure.StoredProcedureDefinition.Parameters.Add("Id", _parameterDefinition); } From 8c73c882648c148fee5f007239b867a37b32d71d Mon Sep 17 00:00:00 2001 From: Alekhya Polavarapu Date: Fri, 31 Oct 2025 11:28:57 -0700 Subject: [PATCH 02/12] Fix the Dollar prefixed columns during serialization --- .../MetadataProviders/Converters/DatabaseObjectConverter.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs index cbfbea5f6b..f6a42625dc 100644 --- a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs +++ b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs @@ -56,7 +56,10 @@ public override void Write(Utf8JsonWriter writer, DatabaseObject value, JsonSeri writer.WriteStartObject(); - // Add TypeName property in DatabaseObject object that we are serializing based on its type. + // Add TypeName property in DatabaseObject object that we are serializing based on its type. (DatabaseTable, DatabaseView) + // We add this property to differentiate between them in the dictionary. This extra property gets used in deserialization above. + // for example if object is DatabaseTable then we need to add + // "TypeName": "Azure.DataApiBuilder.Config.DatabasePrimitives.DatabaseTable, Azure.DataApiBuilder.Config", writer.WriteString(TYPE_NAME, GetTypeNameFromType(value.GetType())); // Add other properties of DatabaseObject From 74e43201227164cf2a254750ab9beb2c8d83960e Mon Sep 17 00:00:00 2001 From: Alekhya Polavarapu Date: Fri, 31 Oct 2025 11:30:07 -0700 Subject: [PATCH 03/12] Fix the Dollar prefixed columns during serialization --- .../MetadataProviders/Converters/DatabaseObjectConverter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs index f6a42625dc..b2a9ed45ee 100644 --- a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs +++ b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs @@ -65,6 +65,7 @@ public override void Write(Utf8JsonWriter writer, DatabaseObject value, JsonSeri // Add other properties of DatabaseObject foreach (PropertyInfo prop in value.GetType().GetProperties()) { + // Skip the TypeName property, as it has been handled above if (prop.Name == TYPE_NAME) { continue; From eec58f678cf1833a3cc52d70322b8e545cb12dd3 Mon Sep 17 00:00:00 2001 From: Alekhya Polavarapu Date: Fri, 31 Oct 2025 12:15:28 -0700 Subject: [PATCH 04/12] Fix the Dollar prefixed columns during serialization --- .../Converters/DatabaseObjectConverter.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs index b2a9ed45ee..a8305e12a9 100644 --- a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs +++ b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs @@ -31,15 +31,12 @@ public override DatabaseObject Read(ref Utf8JsonReader reader, Type typeToConver DatabaseObject objA = (DatabaseObject)JsonSerializer.Deserialize(document, concreteType, options)!; - foreach (PropertyInfo prop in objA.GetType().GetProperties()) + foreach (PropertyInfo prop in objA.GetType().GetProperties().Where(IsSourceDefinitionProperty)) { - if (IsSourceDefinitionProperty(prop)) + SourceDefinition? sourceDef = (SourceDefinition?)prop.GetValue(objA); + if (sourceDef is not null) { - SourceDefinition? sourceDef = (SourceDefinition?)prop.GetValue(objA); - if (sourceDef is not null) - { - UnescapeDollaredColumns(sourceDef); - } + UnescapeDollaredColumns(sourceDef); } } @@ -75,7 +72,8 @@ public override void Write(Utf8JsonWriter writer, DatabaseObject value, JsonSeri object? propVal = prop.GetValue(value); Type propType = prop.PropertyType; - // enforcing that we only escape columns for properties whose type is exactly SourceDefinition(DatabaseTables) + // Only escape columns for properties whose type is exactly SourceDefinition (not subclasses). + // This is because, we do not want unnecessary mutation of subclasses of SourceDefinition unless needed. if (IsSourceDefinitionProperty(prop) && propVal is SourceDefinition sourceDef && propVal.GetType() == typeof(SourceDefinition)) { EscapeDollaredColumns(sourceDef); From 685c1fab4a530dbeb629bf67bdabc9352be3e727 Mon Sep 17 00:00:00 2001 From: Alekhya Polavarapu Date: Tue, 25 Nov 2025 09:16:42 -0800 Subject: [PATCH 05/12] Serialize and deserialize dollared columns succesfully --- .../MetadataProviders/Converters/DatabaseObjectConverter.cs | 5 ++++- .../UnitTests/SerializationDeserializationTests.cs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs index a8305e12a9..2ddddee5e8 100644 --- a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs +++ b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs @@ -18,7 +18,10 @@ public class DatabaseObjectConverter : JsonConverter { private const string TYPE_NAME = "TypeName"; private const string DOLLAR_CHAR = "$"; - private const string ESCAPED_DOLLARCHAR = "_$"; + + // ``DAB_ESCAPE$`` is used to escape column names that start with `$` during serialization. + // It is chosen to be unique enough to avoid collisions with actual column names. + private const string ESCAPED_DOLLARCHAR = "DAB_ESCAPE$"; public override DatabaseObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { diff --git a/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs b/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs index fe4c5ec585..584c1d4682 100644 --- a/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs +++ b/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs @@ -284,7 +284,7 @@ public void TestDictionaryDatabaseObjectSerializationDeserialization() [TestMethod] public void TestDictionaryDatabaseObjectSerializationDeserialization_WithDollarColumn() { - InitializeObjects(true); + InitializeObjects(generateDollaredColumn: true); _options = new() { From 657c7831f77aa933dfb3e9a13d797fd6b6da7f1b Mon Sep 17 00:00:00 2001 From: Alekhya Polavarapu Date: Tue, 25 Nov 2025 10:54:29 -0800 Subject: [PATCH 06/12] Serialize and deserialize dollared columns succesfully --- .../MetadataProviders/Converters/DatabaseObjectConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs index 2ddddee5e8..d1894c7c45 100644 --- a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs +++ b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs @@ -135,7 +135,7 @@ private static void UnescapeDollaredColumns(SourceDefinition sourceDef) { ColumnDefinition col = sourceDef.Columns[key]; sourceDef.Columns.Remove(key); - string newKey = DOLLAR_CHAR + key[2..]; + string newKey = DOLLAR_CHAR + key[11..]; sourceDef.Columns[newKey] = col; } } From 8ea24a7bb5b3839672a54442a4c2947fa51e9b0f Mon Sep 17 00:00:00 2001 From: Alekhya Polavarapu Date: Wed, 3 Dec 2025 20:20:36 -0800 Subject: [PATCH 07/12] Serialize and deserialize dollared columns succesfully --- .../UnitTests/SerializationDeserializationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs b/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs index 1ac6a8ad10..9053f1c8d3 100644 --- a/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs +++ b/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs @@ -293,7 +293,7 @@ public void TestDictionaryDatabaseObjectSerializationDeserialization_WithDollarC new DatabaseObjectConverter(), new TypeConverter() }, - ReferenceHandler = ReferenceHandler.Preserve, + ReferenceHandler = ReferenceHandler.Preserve }; Dictionary dict = new() { { "person", _databaseTable } }; From 2869c88fe3a665a8383420650dcd28a8e8d4ad77 Mon Sep 17 00:00:00 2001 From: Alekhya Polavarapu Date: Thu, 4 Dec 2025 10:06:03 -0800 Subject: [PATCH 08/12] Serialize and deserialize dollared columns succesfully --- .../UnitTests/SerializationDeserializationTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs b/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs index 9053f1c8d3..a4cb2848ad 100644 --- a/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs +++ b/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs @@ -318,7 +318,7 @@ public void TestDictionaryDatabaseObjectSerializationDeserialization_WithDollarC [TestMethod] public void TestDatabaseViewSerializationDeserialization_WithDollarColumn() { - InitializeObjects(true); + InitializeObjects(generateDollaredColumn: true); TestTypeNameChanges(_databaseView, "DatabaseView"); @@ -345,7 +345,7 @@ public void TestDatabaseViewSerializationDeserialization_WithDollarColumn() [TestMethod] public void TestDatabaseStoredProcedureSerializationDeserialization_WithDollarColumn() { - InitializeObjects(true); + InitializeObjects(generateDollaredColumn: true); TestTypeNameChanges(_databaseStoredProcedure, "DatabaseStoredProcedure"); From 71010104cff97fe8e840d560d2a61ae5a21dd726 Mon Sep 17 00:00:00 2001 From: Alekhya Polavarapu Date: Wed, 7 Jan 2026 14:25:50 -0800 Subject: [PATCH 09/12] minor fix for serialization --- .../Converters/DatabaseObjectConverter.cs | 12 ++++---- .../SerializationDeserializationTests.cs | 28 +++++++++++++++---- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs index d1894c7c45..b83bc09890 100644 --- a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs +++ b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs @@ -73,16 +73,14 @@ public override void Write(Utf8JsonWriter writer, DatabaseObject value, JsonSeri writer.WritePropertyName(prop.Name); object? propVal = prop.GetValue(value); - Type propType = prop.PropertyType; - // Only escape columns for properties whose type is exactly SourceDefinition (not subclasses). - // This is because, we do not want unnecessary mutation of subclasses of SourceDefinition unless needed. - if (IsSourceDefinitionProperty(prop) && propVal is SourceDefinition sourceDef && propVal.GetType() == typeof(SourceDefinition)) + // Only escape columns for properties whose type is exactly SourceDefinition. + if (IsSourceDefinitionProperty(prop) && propVal is SourceDefinition sourceDef) { EscapeDollaredColumns(sourceDef); } - JsonSerializer.Serialize(writer, propVal, propType, options); + JsonSerializer.Serialize(writer, propVal, options); } writer.WriteEndObject(); @@ -90,8 +88,8 @@ public override void Write(Utf8JsonWriter writer, DatabaseObject value, JsonSeri private static bool IsSourceDefinitionProperty(PropertyInfo prop) { - // Only return true for properties whose type is exactly SourceDefinition (not subclasses) - return prop.PropertyType == typeof(SourceDefinition); + // Return true for properties whose type is SourceDefinition or any class derived from SourceDefinition + return typeof(SourceDefinition).IsAssignableFrom(prop.PropertyType); } /// diff --git a/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs b/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs index a4cb2848ad..12ae3ee993 100644 --- a/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs +++ b/src/Service.Tests/UnitTests/SerializationDeserializationTests.cs @@ -299,8 +299,11 @@ public void TestDictionaryDatabaseObjectSerializationDeserialization_WithDollarC Dictionary dict = new() { { "person", _databaseTable } }; string serializedDict = JsonSerializer.Serialize(dict, _options); - Dictionary deserializedDict = JsonSerializer.Deserialize>(serializedDict, _options)!; + // Assert that the serialized JSON contains the escaped dollar sign in column name + Assert.IsTrue(serializedDict.Contains("DAB_ESCAPE$FirstName"), + "Serialized JSON should contain the dollar-prefixed column name in SourceDefinition's Columns."); + Dictionary deserializedDict = JsonSerializer.Deserialize>(serializedDict, _options)!; DatabaseTable deserializedDatabaseTable = (DatabaseTable)deserializedDict["person"]; Assert.AreEqual(deserializedDatabaseTable.SourceType, _databaseTable.SourceType); @@ -322,14 +325,22 @@ public void TestDatabaseViewSerializationDeserialization_WithDollarColumn() TestTypeNameChanges(_databaseView, "DatabaseView"); + Dictionary dict = new(); + dict.Add("person", _databaseView); + // Test to catch if there is change in number of properties/fields // Note: On Addition of property make sure it is added in following object creation _databaseView and include in serialization // and deserialization test. int fields = typeof(DatabaseView).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Length; Assert.AreEqual(fields, 6); - string serializedDatabaseView = JsonSerializer.Serialize(_databaseView, _options); - DatabaseView deserializedDatabaseView = JsonSerializer.Deserialize(serializedDatabaseView, _options)!; + string serializedDatabaseView = JsonSerializer.Serialize(dict, _options); + // Assert that the serialized JSON contains the escaped dollar sign in column name + Assert.IsTrue(serializedDatabaseView.Contains("DAB_ESCAPE$FirstName"), + "Serialized JSON should contain the dollar-prefixed column name in SourceDefinition's Columns."); + Dictionary deserializedDict = JsonSerializer.Deserialize>(serializedDatabaseView, _options)!; + + DatabaseView deserializedDatabaseView = (DatabaseView)deserializedDict["person"]; Assert.AreEqual(deserializedDatabaseView.SourceType, _databaseView.SourceType); deserializedDatabaseView.Equals(_databaseView); @@ -349,14 +360,21 @@ public void TestDatabaseStoredProcedureSerializationDeserialization_WithDollarCo TestTypeNameChanges(_databaseStoredProcedure, "DatabaseStoredProcedure"); + Dictionary dict = new(); + dict.Add("person", _databaseStoredProcedure); + // Test to catch if there is change in number of properties/fields // Note: On Addition of property make sure it is added in following object creation _databaseStoredProcedure and include in serialization // and deserialization test. int fields = typeof(DatabaseStoredProcedure).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Length; Assert.AreEqual(fields, 6); - string serializedDatabaseSP = JsonSerializer.Serialize(_databaseStoredProcedure, _options); - DatabaseStoredProcedure deserializedDatabaseSP = JsonSerializer.Deserialize(serializedDatabaseSP, _options)!; + string serializedDatabaseSP = JsonSerializer.Serialize(dict, _options); + // Assert that the serialized JSON contains the escaped dollar sign in column name + Assert.IsTrue(serializedDatabaseSP.Contains("DAB_ESCAPE$FirstName"), + "Serialized JSON should contain the dollar-prefixed column name in SourceDefinition's Columns."); + Dictionary deserializedDict = JsonSerializer.Deserialize>(serializedDatabaseSP, _options)!; + DatabaseStoredProcedure deserializedDatabaseSP = (DatabaseStoredProcedure)deserializedDict["person"]; Assert.AreEqual(deserializedDatabaseSP.SourceType, _databaseStoredProcedure.SourceType); deserializedDatabaseSP.Equals(_databaseStoredProcedure); From 5122098623bfe449d6701e29ae2d5ecade7c3f5c Mon Sep 17 00:00:00 2001 From: Alekhya Polavarapu Date: Wed, 7 Jan 2026 14:34:20 -0800 Subject: [PATCH 10/12] minor fix for serialization --- .../MetadataProviders/Converters/DatabaseObjectConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs index b83bc09890..7b0b9190ea 100644 --- a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs +++ b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs @@ -74,7 +74,7 @@ public override void Write(Utf8JsonWriter writer, DatabaseObject value, JsonSeri writer.WritePropertyName(prop.Name); object? propVal = prop.GetValue(value); - // Only escape columns for properties whose type is exactly SourceDefinition. + // Only escape columns for properties whose type(derived type) is SourceDefinition. if (IsSourceDefinitionProperty(prop) && propVal is SourceDefinition sourceDef) { EscapeDollaredColumns(sourceDef); From 44b0195cd3003449e38b6ef227d4dcdfd10c3524 Mon Sep 17 00:00:00 2001 From: Alekhya Polavarapu Date: Fri, 9 Jan 2026 16:06:59 -0800 Subject: [PATCH 11/12] minor fix for serialization --- .../MetadataProviders/Converters/DatabaseObjectConverter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs index 7b0b9190ea..75e7a7aeca 100644 --- a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs +++ b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs @@ -75,7 +75,7 @@ public override void Write(Utf8JsonWriter writer, DatabaseObject value, JsonSeri object? propVal = prop.GetValue(value); // Only escape columns for properties whose type(derived type) is SourceDefinition. - if (IsSourceDefinitionProperty(prop) && propVal is SourceDefinition sourceDef) + if (IsSourceDefinitionOrDerivedClassProperty(prop) && propVal is SourceDefinition sourceDef) { EscapeDollaredColumns(sourceDef); } @@ -86,7 +86,7 @@ public override void Write(Utf8JsonWriter writer, DatabaseObject value, JsonSeri writer.WriteEndObject(); } - private static bool IsSourceDefinitionProperty(PropertyInfo prop) + private static bool IsSourceDefinitionOrDerivedClassProperty(PropertyInfo prop) { // Return true for properties whose type is SourceDefinition or any class derived from SourceDefinition return typeof(SourceDefinition).IsAssignableFrom(prop.PropertyType); From 4bbeb59507ed3713d261000e73aa32d48a4062cc Mon Sep 17 00:00:00 2001 From: Alekhya-Polavarapu Date: Mon, 12 Jan 2026 11:16:18 -0800 Subject: [PATCH 12/12] Update property filtering in DatabaseObjectConverter --- .../MetadataProviders/Converters/DatabaseObjectConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs index 75e7a7aeca..6d625c7f9d 100644 --- a/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs +++ b/src/Core/Services/MetadataProviders/Converters/DatabaseObjectConverter.cs @@ -34,7 +34,7 @@ public override DatabaseObject Read(ref Utf8JsonReader reader, Type typeToConver DatabaseObject objA = (DatabaseObject)JsonSerializer.Deserialize(document, concreteType, options)!; - foreach (PropertyInfo prop in objA.GetType().GetProperties().Where(IsSourceDefinitionProperty)) + foreach (PropertyInfo prop in objA.GetType().GetProperties().Where(IsSourceDefinitionOrDerivedClassProperty)) { SourceDefinition? sourceDef = (SourceDefinition?)prop.GetValue(objA); if (sourceDef is not null)