diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertySerializer.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertySerializer.cs
index 8e53425613..e5d87b4052 100644
--- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertySerializer.cs
+++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightPropertySerializer.cs
@@ -374,7 +374,7 @@ private void WriteNullProperty(
// TODO: Enable updating top-level properties to null #645
throw new ODataException("A null top-level property is not allowed to be serialized.");
}
- else
+ else if (!this.MessageWriterSettings.IgnoreNullValues)
{
this.JsonWriter.WriteName(property.Name);
this.JsonLightValueSerializer.WriteNullValue();
diff --git a/src/Microsoft.OData.Core/ODataMessageWriterSettings.cs b/src/Microsoft.OData.Core/ODataMessageWriterSettings.cs
index 731e19399d..51332a093b 100644
--- a/src/Microsoft.OData.Core/ODataMessageWriterSettings.cs
+++ b/src/Microsoft.OData.Core/ODataMessageWriterSettings.cs
@@ -177,6 +177,14 @@ public ODataUri ODataUri
///
internal bool ThrowOnDuplicatePropertyNames { get; private set; }
+ ///
+ /// Don't serialize null values
+ ///
+ ///
+ /// Default valus is false, that means serialize null values.
+ ///
+ public bool IgnoreNullValues { get; set; }
+
///
/// Returns whether ThrowOnUndeclaredPropertyForNonOpenType validation setting is enabled.
///
@@ -398,6 +406,7 @@ private void CopyFrom(ODataMessageWriterSettings other)
this.shouldIncludeAnnotation = other.shouldIncludeAnnotation;
this.useFormat = other.useFormat;
this.Version = other.Version;
+ this.IgnoreNullValues = other.IgnoreNullValues;
this.validations = other.validations;
this.ThrowIfTypeConflictsWithMetadata = other.ThrowIfTypeConflictsWithMetadata;
diff --git a/test/FunctionalTests/Tests/DataOData/Tests/OData.Common.Tests/PublicApi/PublicApi.bsl b/test/FunctionalTests/Tests/DataOData/Tests/OData.Common.Tests/PublicApi/PublicApi.bsl
index e10cb0b6c9..a0964e9b11 100644
--- a/test/FunctionalTests/Tests/DataOData/Tests/OData.Common.Tests/PublicApi/PublicApi.bsl
+++ b/test/FunctionalTests/Tests/DataOData/Tests/OData.Common.Tests/PublicApi/PublicApi.bsl
@@ -4927,6 +4927,7 @@ public sealed class Microsoft.OData.ODataMessageWriterSettings {
System.Uri BaseUri { public get; public set; }
bool EnableCharactersCheck { public get; public set; }
bool EnableMessageStreamDisposal { public get; public set; }
+ bool IgnoreNullValues { public get; public set; }
string JsonPCallback { public get; public set; }
Microsoft.OData.ODataMessageQuotas MessageQuotas { public get; public set; }
Microsoft.OData.ODataUri ODataUri { public get; public set; }
diff --git a/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/JsonLight/JsonLightEntryWriterTests.IgnoreNullPropertiesInEntryTest.approved.txt b/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/JsonLight/JsonLightEntryWriterTests.IgnoreNullPropertiesInEntryTest.approved.txt
new file mode 100644
index 0000000000..3578c7bcee
--- /dev/null
+++ b/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/JsonLight/JsonLightEntryWriterTests.IgnoreNullPropertiesInEntryTest.approved.txt
@@ -0,0 +1,32 @@
+Combination: 1; TestConfiguration = Format: JsonLight, Request: True, Synchronous: True
+Model Present: true
+{"@odata.context":"http://odata.org/test/$metadata#CustomerSet/$entity","ID":42,"Hobby":"Hiking"}
+
+Combination: 2; TestConfiguration = Format: JsonLight, Request: False, Synchronous: True
+Model Present: true
+{"@odata.context":"http://odata.org/test/$metadata#CustomerSet/$entity","ID":42,"Hobby":"Hiking"}
+
+Combination: 3; TestConfiguration = Format: JsonLight, Request: True, Synchronous: False
+Model Present: true
+{"@odata.context":"http://odata.org/test/$metadata#CustomerSet/$entity","ID":42,"Hobby":"Hiking"}
+
+Combination: 4; TestConfiguration = Format: JsonLight, Request: False, Synchronous: False
+Model Present: true
+{"@odata.context":"http://odata.org/test/$metadata#CustomerSet/$entity","ID":42,"Hobby":"Hiking"}
+
+Combination: 5; TestConfiguration = Format: JsonLight, Request: True, Synchronous: True
+Model Present: true
+{"@odata.context":"http://odata.org/test/$metadata#CustomerSet/$entity","ID":44}
+
+Combination: 6; TestConfiguration = Format: JsonLight, Request: False, Synchronous: True
+Model Present: true
+{"@odata.context":"http://odata.org/test/$metadata#CustomerSet/$entity","ID":44}
+
+Combination: 7; TestConfiguration = Format: JsonLight, Request: True, Synchronous: False
+Model Present: true
+{"@odata.context":"http://odata.org/test/$metadata#CustomerSet/$entity","ID":44}
+
+Combination: 8; TestConfiguration = Format: JsonLight, Request: False, Synchronous: False
+Model Present: true
+{"@odata.context":"http://odata.org/test/$metadata#CustomerSet/$entity","ID":44}
+
diff --git a/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/JsonLight/JsonLightEntryWriterTests.cs b/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/JsonLight/JsonLightEntryWriterTests.cs
index f7491a77e3..3056e96869 100644
--- a/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/JsonLight/JsonLightEntryWriterTests.cs
+++ b/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/JsonLight/JsonLightEntryWriterTests.cs
@@ -48,7 +48,7 @@ public void PayloadOrderTest()
var nonMLEBaseType = new EdmEntityType("TestModel", "NonMLEBaseType");
nonMLEBaseType.AddKeys(nonMLEBaseType.AddStructuralProperty("ID", EdmCoreModel.Instance.GetInt32(false)));
model.AddElement(nonMLEBaseType);
- var nonMLESet = container.AddEntitySet("NonMLESet", nonMLEBaseType);
+ var nonMLESet = container.AddEntitySet("NonMLESet", nonMLEBaseType);
var nonMLEType = new EdmEntityType("TestModel", "NonMLEType", nonMLEBaseType);
nonMLEType.AddStructuralProperty("Name", EdmCoreModel.Instance.GetString(true));
@@ -66,13 +66,13 @@ public void PayloadOrderTest()
var mleType = new EdmEntityType("TestModel", "MLEType", mleBaseType, false, false, true);
mleType.AddStructuralProperty("Name", EdmCoreModel.Instance.GetString(true));
mleType.AddStructuralProperty("Description", EdmCoreModel.Instance.GetString(true));
- mleType.AddStructuralProperty("StreamProperty", EdmPrimitiveTypeKind.Stream, isNullable:false);
+ mleType.AddStructuralProperty("StreamProperty", EdmPrimitiveTypeKind.Stream, isNullable: false);
var mleNav = mleType.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo { Name = "NavProp", Target = otherType, TargetMultiplicity = EdmMultiplicity.Many });
mleSet.AddNavigationTarget(mleNav, otherset);
model.AddElement(mleType);
IEnumerable testCases = new[]
- {
+ {
new EntryPayloadTestCase
{
DebugDescription = "TypeName at the beginning, nothing else",
@@ -96,7 +96,7 @@ public void PayloadOrderTest()
new ODataProperty { Name = "Name", Value = "test" },
}
}
- .WithAnnotation(new WriteEntryCallbacksAnnotation
+ .WithAnnotation(new WriteEntryCallbacksAnnotation
{
BeforeWriteStartCallback = (entry) => { entry.TypeName = "TestModel.MLEType"; },
BeforeWriteEndCallback = (entry) => { entry.TypeName = "NonExistingType"; }
@@ -197,16 +197,16 @@ public void PayloadOrderTest()
new ODataProperty { Name = "ID", Value = (int)42 },
new ODataProperty { Name = "Name", Value = "test" },
}
- }.WithAnnotation(new WriteEntryCallbacksAnnotation
+ }.WithAnnotation(new WriteEntryCallbacksAnnotation
{
BeforeWriteStartCallback = (entry) =>
- {
+ {
entry.EditLink = null;
entry.MediaResource.EditLink = null;
entry.MediaResource.ETag = null;
entry.MediaResource.ContentType = null;
},
- BeforeWriteEndCallback = (entry) =>
+ BeforeWriteEndCallback = (entry) =>
{
entry.EditLink = new Uri("http://odata.org/editlink");
entry.MediaResource.EditLink = new Uri("http://odata.org/mediaeditlink");
@@ -240,7 +240,7 @@ public void PayloadOrderTest()
testCase.Items,
tc => new JsonWriterTestExpectedResults(this.Settings.ExpectedResultSettings)
{
- Json = string.Format(CultureInfo.InvariantCulture, testCase.Json,
+ Json = string.Format(CultureInfo.InvariantCulture, testCase.Json,
string.Empty,
JsonLightWriterUtils.GetMetadataUrlPropertyForEntry(testCase.EntitySet.Name) + ","),
FragmentExtractor = (result) => result.RemoveAllAnnotations(true)
@@ -338,7 +338,7 @@ public void ActionAndFunctionPayloadOrderTest()
model.AddElement(nonMLEType);
container.AddEntitySet("NonMLEType", nonMLEType);
- ODataAction action = new ODataAction
+ ODataAction action = new ODataAction
{
Metadata = new Uri("http://odata.org/test/$metadata#defaultAction"),
Title = "Default Action",
@@ -432,6 +432,96 @@ public void ActionAndFunctionPayloadOrderTest()
});
}
+ [TestMethod, Variation(Description = "Test skipping null values when writing JSON Lite entries with IgnoreNullValues = true.")]
+ public void IgnoreNullPropertiesInEntryTest()
+ {
+ EdmModel model = new EdmModel();
+ var container = new EdmEntityContainer("TestModel", "TestContainer");
+ model.AddElement(container);
+
+ var customerType = new EdmEntityType("TestModel", "CustomerType", null, false, isOpen: false);
+ customerType.AddKeys(customerType.AddStructuralProperty("ID", EdmCoreModel.Instance.GetInt32(false)));
+ customerType.AddKeys(customerType.AddStructuralProperty("Hobby", EdmCoreModel.Instance.GetString(true)));
+ model.AddElement(customerType);
+ var customerSet = container.AddEntitySet("CustomerSet", customerType);
+
+ var addressType = new EdmComplexType("TestModel", "AddressType");
+ addressType.AddStructuralProperty("Street", EdmCoreModel.Instance.GetStream(true));
+ model.AddElement(addressType);
+
+ IEnumerable testCases = new[]
+ {
+ new EntryPayloadTestCase
+ {
+ DebugDescription = "Customer instance with all properties set.",
+ Items = new[] { new ODataResource()
+ {
+ TypeName = "TestModel.CustomerType",
+ Properties = new ODataProperty[]
+ {
+ new ODataProperty { Name = "ID", Value = (int)42 },
+ new ODataProperty { Name = "Hobby", Value = "Hiking" },
+ }
+ } },
+ Model = model,
+ EntitySet = customerSet,
+ Json = string.Join("$(NL)",
+ "{{",
+ "{0}\"ID\":\"42\", \"Hobby\":\"Hiking\"",
+ "}}")
+ },
+ new EntryPayloadTestCase
+ {
+ DebugDescription = "Customer instance without Hobby.",
+ Items = new[] {new ODataResource()
+ {
+ TypeName = "TestModel.CustomerType",
+ Properties = new ODataProperty[]
+ {
+ new ODataProperty { Name = "ID", Value = (int)44 },
+ new ODataProperty { Name = "Hobby", Value = null },
+ }
+ } },
+ Model = model,
+ EntitySet = customerSet,
+ Json = string.Join("$(NL)",
+ "{{",
+ "{0}\"ID\":\"44\"",
+ "}}")
+ },
+
+ };
+
+ IEnumerable> testDescriptors = testCases.Select(testCase =>
+ new PayloadWriterTestDescriptor(
+ this.Settings,
+ testCase.Items,
+ tc => new JsonWriterTestExpectedResults(this.Settings.ExpectedResultSettings)
+ {
+ Json = string.Format(
+ CultureInfo.InvariantCulture,
+ testCase.Json,
+ JsonLightWriterUtils.GetMetadataUrlPropertyForEntry(testCase.EntitySet.Name) + ","),
+ FragmentExtractor = (result) => result.RemoveAllAnnotations(true)
+ })
+ {
+ DebugDescription = testCase.DebugDescription,
+ Model = testCase.Model,
+ PayloadEdmElementContainer = testCase.EntitySet,
+ PayloadEdmElementType = testCase.EntityType,
+ SkipTestConfiguration = testCase.SkipTestConfiguration
+ });
+
+ this.CombinatorialEngineProvider.RunCombinations(
+ testDescriptors,
+ this.WriterTestConfigurationProvider.JsonLightFormatConfigurationsWithIndent,
+ (testDescriptor, testConfiguration) =>
+ {
+ testConfiguration.MessageWriterSettings.IgnoreNullValues = true;
+ TestWriterUtils.WriteAndVerifyODataEdmPayload(testDescriptor, testConfiguration, this.Assert, this.Logger);
+ });
+ }
+
[TestMethod, Variation(Description = "Test correct serialization format when writing JSON Lite entries with open properties.")]
public void OpenPropertiesInEntryTest()
{
@@ -439,7 +529,7 @@ public void OpenPropertiesInEntryTest()
var container = new EdmEntityContainer("TestModel", "TestContainer");
model.AddElement(container);
- var openCustomerType = new EdmEntityType("TestModel", "OpenCustomerType", null, false, isOpen:true);
+ var openCustomerType = new EdmEntityType("TestModel", "OpenCustomerType", null, false, isOpen: true);
openCustomerType.AddKeys(openCustomerType.AddStructuralProperty("ID", EdmCoreModel.Instance.GetInt32(false)));
model.AddElement(openCustomerType);
var customerSet = container.AddEntitySet("CustomerSet", openCustomerType);
@@ -494,7 +584,7 @@ public void OpenPropertiesInEntryTest()
"\"type\":\"name\",\"properties\":{{",
"\"name\":\"EPSG:4326\"",
"}}",
- "}}",
+ "}}",
"}}",
"}}")
},
@@ -526,7 +616,7 @@ public void OpenPropertiesInEntryTest()
tc => new JsonWriterTestExpectedResults(this.Settings.ExpectedResultSettings)
{
Json = string.Format(
- CultureInfo.InvariantCulture,
+ CultureInfo.InvariantCulture,
testCase.Json,
JsonLightWriterUtils.GetMetadataUrlPropertyForEntry(testCase.EntitySet.Name) + ","),
FragmentExtractor = (result) => result.RemoveAllAnnotations(true)
@@ -561,7 +651,7 @@ public void SpatialPropertiesInEntryTest()
customerType.AddStructuralProperty("Location2", EdmCoreModel.Instance.GetSpatial(EdmPrimitiveTypeKind.GeographyPoint, false));
model.AddElement(customerType);
var customerSet = container.AddEntitySet("CustomerSet", customerType);
-
+
ISpatial pointValue = GeographyFactory.Point(32.0, -100.0).Build();
IEnumerable testCases = new[]
@@ -590,7 +680,7 @@ public void SpatialPropertiesInEntryTest()
"\"type\":\"name\",\"properties\":{{",
"\"name\":\"EPSG:4326\"",
"}}",
- "}}",
+ "}}",
"}}",
"}}")
},
@@ -617,7 +707,7 @@ public void SpatialPropertiesInEntryTest()
"\"type\":\"name\",\"properties\":{{",
"\"name\":\"EPSG:4326\"",
"}}",
- "}}",
+ "}}",
"}}",
"}}")
},
diff --git a/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/Microsoft.Test.Taupo.OData.Writer.Tests.csproj b/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/Microsoft.Test.Taupo.OData.Writer.Tests.csproj
index d9feb236ef..d1d7ea011e 100644
--- a/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/Microsoft.Test.Taupo.OData.Writer.Tests.csproj
+++ b/test/FunctionalTests/Tests/DataOData/Tests/OData.Writer.Tests/Microsoft.Test.Taupo.OData.Writer.Tests.csproj
@@ -182,6 +182,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest