Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignore Null Values in output #944

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
9 changes: 9 additions & 0 deletions src/Microsoft.OData.Core/ODataMessageWriterSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,14 @@ public ODataUri ODataUri
/// </summary>
internal bool ThrowOnDuplicatePropertyNames { get; private set; }

/// <summary>
/// Don't serialize null values
/// </summary>
/// <remarks>
/// Default valus is false, that means serialize null values.
/// </remarks>
public bool IgnoreNullValues { get; set; }

/// <summary>
/// Returns whether ThrowOnUndeclaredPropertyForNonOpenType validation setting is enabled.
/// </summary>
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
Original file line number Diff line number Diff line change
@@ -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}

Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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<EntryPayloadTestCase> testCases = new[]
{
{
new EntryPayloadTestCase
{
DebugDescription = "TypeName at the beginning, nothing else",
Expand All @@ -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"; }
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -432,14 +432,104 @@ 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<EntryPayloadTestCase> 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<PayloadWriterTestDescriptor<ODataItem>> testDescriptors = testCases.Select(testCase =>
new PayloadWriterTestDescriptor<ODataItem>(
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()
{
EdmModel model = new EdmModel();
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);
Expand Down Expand Up @@ -494,7 +584,7 @@ public void OpenPropertiesInEntryTest()
"\"type\":\"name\",\"properties\":{{",
"\"name\":\"EPSG:4326\"",
"}}",
"}}",
"}}",
"}}",
"}}")
},
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<EntryPayloadTestCase> testCases = new[]
Expand Down Expand Up @@ -590,7 +680,7 @@ public void SpatialPropertiesInEntryTest()
"\"type\":\"name\",\"properties\":{{",
"\"name\":\"EPSG:4326\"",
"}}",
"}}",
"}}",
"}}",
"}}")
},
Expand All @@ -617,7 +707,7 @@ public void SpatialPropertiesInEntryTest()
"\"type\":\"name\",\"properties\":{{",
"\"name\":\"EPSG:4326\"",
"}}",
"}}",
"}}",
"}}",
"}}")
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@
<Content Include="JsonLight\JsonLightEntryWriterTests.OpenPropertiesInEntryTest.approved.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="JsonLight\JsonLightEntryWriterTests.IgnoreNullPropertiesInEntryTest.approved.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="JsonLight\JsonLightPropertyWriterTests.WriteFloatingPointValue.approved.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Expand Down