From bc726f1b6de78a4455b588865e328184afd7ed9a Mon Sep 17 00:00:00 2001 From: JamesNK Date: Mon, 1 Aug 2011 02:05:14 +0000 Subject: [PATCH] -Added Populate and IgnoreAndPopulate options to DefaultValueHandling for automatically populating default values during deserialization --- .../Schema/JsonSchemaGeneratorTests.cs | 15 ++++++ .../DefaultValueHandlingTests.cs | 20 +++++++- .../Serialization/JsonSerializerTest.cs | 18 ++++++- .../JsonPropertyWithHandlingValues.cs | 8 +++ Src/Newtonsoft.Json/DefaultValueHandling.cs | 18 +++++-- Src/Newtonsoft.Json/JsonSerializer.cs | 2 +- .../Schema/JsonSchemaGenerator.cs | 7 ++- .../JsonSerializerInternalReader.cs | 50 +++++++++++++------ .../JsonSerializerInternalWriter.cs | 9 +++- 9 files changed, 123 insertions(+), 24 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Schema/JsonSchemaGeneratorTests.cs b/Src/Newtonsoft.Json.Tests/Schema/JsonSchemaGeneratorTests.cs index ed8a6d4d..9080aea2 100644 --- a/Src/Newtonsoft.Json.Tests/Schema/JsonSchemaGeneratorTests.cs +++ b/Src/Newtonsoft.Json.Tests/Schema/JsonSchemaGeneratorTests.cs @@ -631,6 +631,21 @@ namespace Newtonsoft.Json.Tests.Schema ], ""default"": ""Default!"" }, + ""DefaultValueHandlingPopulateProperty"": { + ""required"": true, + ""type"": [ + ""string"", + ""null"" + ], + ""default"": ""Default!"" + }, + ""DefaultValueHandlingIgnoreAndPopulateProperty"": { + ""type"": [ + ""string"", + ""null"" + ], + ""default"": ""Default!"" + }, ""NullValueHandlingIgnoreProperty"": { ""type"": [ ""string"", diff --git a/Src/Newtonsoft.Json.Tests/Serialization/DefaultValueHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/DefaultValueHandlingTests.cs index 9d14e336..4c90247a 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/DefaultValueHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/DefaultValueHandlingTests.cs @@ -50,7 +50,7 @@ namespace Newtonsoft.Json.Tests.Serialization } [Test] - public void DefaultValueAttributeTest() + public void SerializeDefaultValueAttributeTest() { string json = JsonConvert.SerializeObject(new DefaultValueAttributeTestClass(), Formatting.None, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore }); @@ -69,6 +69,24 @@ namespace Newtonsoft.Json.Tests.Serialization Assert.AreEqual(@"{}", json); } + [Test] + public void DeserializeDefaultValueAttributeTest() + { + string json = "{}"; + + DefaultValueAttributeTestClass c = JsonConvert.DeserializeObject(json, new JsonSerializerSettings + { + DefaultValueHandling = DefaultValueHandling.Populate + }); + Assert.AreEqual("TestProperty1Value", c.TestProperty1); + + c = JsonConvert.DeserializeObject(json, new JsonSerializerSettings + { + DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate + }); + Assert.AreEqual("TestProperty1Value", c.TestProperty1); + } + [JsonObject] public class NetworkUser { diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index f24f3ff2..3d03c081 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -1300,16 +1300,19 @@ keyword such as type of business."" } [Test] - public void JsonPropertyWithHandlingValues() + public void SerializeJsonPropertyWithHandlingValues() { JsonPropertyWithHandlingValues o = new JsonPropertyWithHandlingValues(); o.DefaultValueHandlingIgnoreProperty = "Default!"; o.DefaultValueHandlingIncludeProperty = "Default!"; + o.DefaultValueHandlingPopulateProperty = "Default!"; + o.DefaultValueHandlingIgnoreAndPopulateProperty = "Default!"; string json = JsonConvert.SerializeObject(o, Formatting.Indented); Assert.AreEqual(@"{ ""DefaultValueHandlingIncludeProperty"": ""Default!"", + ""DefaultValueHandlingPopulateProperty"": ""Default!"", ""NullValueHandlingIncludeProperty"": null, ""ReferenceLoopHandlingErrorProperty"": null, ""ReferenceLoopHandlingIgnoreProperty"": null, @@ -1320,10 +1323,23 @@ keyword such as type of business."" Assert.AreEqual(@"{ ""DefaultValueHandlingIncludeProperty"": ""Default!"", + ""DefaultValueHandlingPopulateProperty"": ""Default!"", ""NullValueHandlingIncludeProperty"": null }", json); } + [Test] + public void DeserializeJsonPropertyWithHandlingValues() + { + string json = "{}"; + + JsonPropertyWithHandlingValues o = JsonConvert.DeserializeObject(json); + Assert.AreEqual("Default!", o.DefaultValueHandlingIgnoreAndPopulateProperty); + Assert.AreEqual("Default!", o.DefaultValueHandlingPopulateProperty); + Assert.AreEqual(null, o.DefaultValueHandlingIgnoreProperty); + Assert.AreEqual(null, o.DefaultValueHandlingIncludeProperty); + } + [Test] [ExpectedException(typeof(JsonSerializationException))] public void JsonPropertyWithHandlingValues_ReferenceLoopError() diff --git a/Src/Newtonsoft.Json.Tests/TestObjects/JsonPropertyWithHandlingValues.cs b/Src/Newtonsoft.Json.Tests/TestObjects/JsonPropertyWithHandlingValues.cs index a844b547..2f8fba9a 100644 --- a/Src/Newtonsoft.Json.Tests/TestObjects/JsonPropertyWithHandlingValues.cs +++ b/Src/Newtonsoft.Json.Tests/TestObjects/JsonPropertyWithHandlingValues.cs @@ -37,6 +37,14 @@ namespace Newtonsoft.Json.Tests.TestObjects [DefaultValue("Default!")] public string DefaultValueHandlingIncludeProperty { get; set; } + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + [DefaultValue("Default!")] + public string DefaultValueHandlingPopulateProperty { get; set; } + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + [DefaultValue("Default!")] + public string DefaultValueHandlingIgnoreAndPopulateProperty { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string NullValueHandlingIgnoreProperty { get; set; } diff --git a/Src/Newtonsoft.Json/DefaultValueHandling.cs b/Src/Newtonsoft.Json/DefaultValueHandling.cs index 570d6843..72c265fa 100644 --- a/Src/Newtonsoft.Json/DefaultValueHandling.cs +++ b/Src/Newtonsoft.Json/DefaultValueHandling.cs @@ -33,15 +33,27 @@ namespace Newtonsoft.Json /// /// Specifies default value handling options for the . /// + [Flags] public enum DefaultValueHandling { /// - /// Include default values when serializing and deserializing objects. + /// Include members where the member value is the same as the member's default value when serializing objects. + /// Included members are written to JSON. Has no effect when deserializing. /// Include = 0, /// - /// Ignore default values when serializing and deserializing objects. + /// Ignore members where the member value is the same as the member's default value when serializing objects + /// so that is is not written to JSON, and ignores setting members when the JSON value equals the member's default value. /// - Ignore = 1 + Ignore = 1, + /// + /// Members with a default value but no JSON will be set to their default value when deserializing. + /// + Populate = 2, + /// + /// Ignore members where the member value is the same as the member's default value when serializing objects + /// and sets members to their default value when deserializing. + /// + IgnoreAndPopulate = Ignore | Populate } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonSerializer.cs b/Src/Newtonsoft.Json/JsonSerializer.cs index e711c9f4..d1bcc168 100644 --- a/Src/Newtonsoft.Json/JsonSerializer.cs +++ b/Src/Newtonsoft.Json/JsonSerializer.cs @@ -200,7 +200,7 @@ namespace Newtonsoft.Json get { return _defaultValueHandling; } set { - if (value < DefaultValueHandling.Include || value > DefaultValueHandling.Ignore) + if (value < DefaultValueHandling.Include || value > DefaultValueHandling.IgnoreAndPopulate) throw new ArgumentOutOfRangeException("value"); _defaultValueHandling = value; diff --git a/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs b/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs index feb6a41d..cf6408b4 100644 --- a/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs +++ b/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs @@ -352,6 +352,11 @@ namespace Newtonsoft.Json.Schema return type; } + private bool HasFlag(DefaultValueHandling value, DefaultValueHandling flag) + { + return ((value & flag) == flag); + } + private void GenerateObjectSchema(Type type, JsonObjectContract contract) { CurrentSchema.Properties = new Dictionary(); @@ -360,7 +365,7 @@ namespace Newtonsoft.Json.Schema if (!property.Ignored) { bool optional = property.NullValueHandling == NullValueHandling.Ignore || - property.DefaultValueHandling == DefaultValueHandling.Ignore || + HasFlag(property.DefaultValueHandling.GetValueOrDefault(), DefaultValueHandling.Ignore) || property.ShouldSerialize != null || property.GetIsSpecified != null; diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 17ca8287..ce80f092 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -555,9 +555,9 @@ namespace Newtonsoft.Json.Serialization } // test tokentype here because default value might not be convertable to actual type, e.g. default of "" for DateTime - if (property.DefaultValueHandling.GetValueOrDefault(Serializer.DefaultValueHandling) == DefaultValueHandling.Ignore + if (HasFlag(property.DefaultValueHandling.GetValueOrDefault(Serializer.DefaultValueHandling), DefaultValueHandling.Ignore) && JsonReader.IsPrimitiveToken(reader.TokenType) - && Equals(reader.Value, property.DefaultValue)) + && MiscellaneousUtils.ValueEquals(reader.Value, property.DefaultValue)) { reader.Skip(); return; @@ -579,12 +579,18 @@ namespace Newtonsoft.Json.Serialization } } + private bool HasFlag(DefaultValueHandling value, DefaultValueHandling flag) + { + return ((value & flag) == flag); + } + private bool ShouldSetPropertyValue(JsonProperty property, object value) { if (property.NullValueHandling.GetValueOrDefault(Serializer.NullValueHandling) == NullValueHandling.Ignore && value == null) return false; - if (property.DefaultValueHandling.GetValueOrDefault(Serializer.DefaultValueHandling) == DefaultValueHandling.Ignore && MiscellaneousUtils.ValueEquals(value, property.DefaultValue)) + if (HasFlag(property.DefaultValueHandling.GetValueOrDefault(Serializer.DefaultValueHandling), DefaultValueHandling.Ignore) + && MiscellaneousUtils.ValueEquals(value, property.DefaultValue)) return false; if (!property.Writable) @@ -1065,8 +1071,8 @@ namespace Newtonsoft.Json.Serialization { contract.InvokeOnDeserializing(newObject, Serializer.Context); - Dictionary requiredProperties = - contract.Properties.Where(m => m.Required != Required.Default).ToDictionary(m => m, m => RequiredValue.None); + Dictionary propertiesPresence = + contract.Properties.ToDictionary(m => m, m => PropertyPresence.None); if (id != null) Serializer.ReferenceResolver.AddReference(this, id, newObject); @@ -1098,7 +1104,7 @@ namespace Newtonsoft.Json.Serialization if (!ReadForType(reader, property.PropertyType, property.Converter)) throw new JsonSerializationException("Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName)); - SetRequiredProperty(reader, property, requiredProperties); + SetPropertyPresence(reader, property, propertiesPresence); SetPropertyValue(property, reader, newObject); } @@ -1111,12 +1117,26 @@ namespace Newtonsoft.Json.Serialization } break; case JsonToken.EndObject: - foreach (KeyValuePair requiredProperty in requiredProperties) + foreach (KeyValuePair propertyPresence in propertiesPresence) { - if (requiredProperty.Value == RequiredValue.None) - throw new JsonSerializationException("Required property '{0}' not found in JSON.".FormatWith(CultureInfo.InvariantCulture, requiredProperty.Key.PropertyName)); - if (requiredProperty.Key.Required == Required.Always && requiredProperty.Value == RequiredValue.Null) - throw new JsonSerializationException("Required property '{0}' expects a value but got null.".FormatWith(CultureInfo.InvariantCulture, requiredProperty.Key.PropertyName)); + JsonProperty property = propertyPresence.Key; + PropertyPresence presence = propertyPresence.Value; + + switch (presence) + { + case PropertyPresence.None: + if (property.Required == Required.AllowNull || property.Required == Required.Always) + throw new JsonSerializationException("Required property '{0}' not found in JSON.".FormatWith(CultureInfo.InvariantCulture, property.PropertyName)); + + if (HasFlag(property.DefaultValueHandling.GetValueOrDefault(Serializer.DefaultValueHandling), DefaultValueHandling.Populate) + && property.Writable) + property.ValueProvider.SetValue(newObject, EnsureType(property.DefaultValue, CultureInfo.InvariantCulture, property.PropertyType)); + break; + case PropertyPresence.Null: + if (property.Required == Required.Always) + throw new JsonSerializationException("Required property '{0}' expects a value but got null.".FormatWith(CultureInfo.InvariantCulture, property.PropertyName)); + break; + } } contract.InvokeOnDeserialized(newObject, Serializer.Context); @@ -1132,13 +1152,13 @@ namespace Newtonsoft.Json.Serialization throw new JsonSerializationException("Unexpected end when deserializing object."); } - private void SetRequiredProperty(JsonReader reader, JsonProperty property, Dictionary requiredProperties) + private void SetPropertyPresence(JsonReader reader, JsonProperty property, Dictionary requiredProperties) { if (property != null) { requiredProperties[property] = (reader.TokenType == JsonToken.Null || reader.TokenType == JsonToken.Undefined) - ? RequiredValue.Null - : RequiredValue.Value; + ? PropertyPresence.Null + : PropertyPresence.Value; } } @@ -1154,7 +1174,7 @@ namespace Newtonsoft.Json.Serialization } } - internal enum RequiredValue + internal enum PropertyPresence { None, Null, diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index d70a4434..8608fead 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -202,8 +202,8 @@ namespace Newtonsoft.Json.Serialization memberValue == null) return; - if (property.DefaultValueHandling.GetValueOrDefault(Serializer.DefaultValueHandling) == - DefaultValueHandling.Ignore && MiscellaneousUtils.ValueEquals(memberValue, defaultValue)) + if (HasFlag(property.DefaultValueHandling.GetValueOrDefault(Serializer.DefaultValueHandling), DefaultValueHandling.Ignore) + && MiscellaneousUtils.ValueEquals(memberValue, defaultValue)) return; if (ShouldWriteReference(memberValue, property, contract)) @@ -360,6 +360,11 @@ namespace Newtonsoft.Json.Serialization writer.WriteValue(ReflectionUtils.GetTypeName(type, Serializer.TypeNameAssemblyFormat, Serializer.Binder)); } + private bool HasFlag(DefaultValueHandling value, DefaultValueHandling flag) + { + return ((value & flag) == flag); + } + private bool HasFlag(PreserveReferencesHandling value, PreserveReferencesHandling flag) { return ((value & flag) == flag);