diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 82b5f58e..c3283e06 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -3378,6 +3378,138 @@ keyword such as type of business."" Console.WriteLine(json); } + public class SpecifiedTestClass + { + private bool _nameSpecified; + + public string Name { get; set; } + public int Age { get; set; } + public int Weight { get; set; } + + // dummy. should never be used because it isn't of type bool + [JsonIgnore] + public long AgeSpecified { get; set; } + + [JsonIgnore] + public bool NameSpecified + { + get { return _nameSpecified; } + set { _nameSpecified = value; } + } + + [JsonIgnore] + public bool WeightSpecified; + } + + [Test] + public void SpecifiedTest() + { + SpecifiedTestClass c = new SpecifiedTestClass(); + c.Name = "James"; + c.Age = 27; + c.NameSpecified = false; + + string json = JsonConvert.SerializeObject(c, Formatting.Indented); + + Assert.AreEqual(@"{ + ""Age"": 27 +}", json); + + SpecifiedTestClass deserialized = JsonConvert.DeserializeObject(json); + Assert.IsNull(deserialized.Name); + Assert.IsFalse(deserialized.NameSpecified); + Assert.IsFalse(deserialized.WeightSpecified); + Assert.AreEqual(27, deserialized.Age); + + c.NameSpecified = true; + c.WeightSpecified = true; + json = JsonConvert.SerializeObject(c, Formatting.Indented); + + Assert.AreEqual(@"{ + ""Name"": ""James"", + ""Age"": 27, + ""Weight"": 0 +}", json); + + deserialized = JsonConvert.DeserializeObject(json); + Assert.AreEqual("James", deserialized.Name); + Assert.IsTrue(deserialized.NameSpecified); + Assert.IsTrue(deserialized.WeightSpecified); + Assert.AreEqual(27, deserialized.Age); + +// XmlSerializer s = new XmlSerializer(typeof(OptionalOrder)); + +// StringWriter sw = new StringWriter(); +// s.Serialize(sw, new OptionalOrder() { FirstOrder = "First", FirstOrderSpecified = true}); + +// Console.WriteLine(sw.ToString()); + +// string xml = @" +// +// First +//"; + +// OptionalOrder o = (OptionalOrder)s.Deserialize(new StringReader(xml)); +// Console.WriteLine(o.FirstOrder); +// Console.WriteLine(o.FirstOrderSpecified); + } + + //public class OptionalOrder + //{ + // // This field shouldn't be serialized + // // if it is uninitialized. + // public string FirstOrder; + // // Use the XmlIgnoreAttribute to ignore the + // // special field named "FirstOrderSpecified". + // [System.Xml.Serialization.XmlIgnoreAttribute] + // public bool FirstOrderSpecified; + //} + + public class FamilyDetails + { + public string Name { get; set; } + public int NumberOfChildren { get; set; } + + [JsonIgnore] + public bool NumberOfChildrenSpecified { get; set; } + } + + [Test] + public void SpecifiedExample() + { + FamilyDetails joe = new FamilyDetails(); + joe.Name = "Joe Family Details"; + joe.NumberOfChildren = 4; + joe.NumberOfChildrenSpecified = true; + + FamilyDetails martha = new FamilyDetails(); + martha.Name = "Martha Family Details"; + martha.NumberOfChildren = 3; + martha.NumberOfChildrenSpecified = false; + + string json = JsonConvert.SerializeObject(new[] { joe, martha }, Formatting.Indented); + //[ + // { + // "Name": "Joe Family Details", + // "NumberOfChildren": 4 + // }, + // { + // "Name": "Martha Family Details" + // } + //] + Console.WriteLine(json); + + string mikeString = "{\"Name\": \"Mike Person\"}"; + FamilyDetails mike = JsonConvert.DeserializeObject(mikeString); + + Console.WriteLine("mikeString specifies number of children: {0}", mike.NumberOfChildrenSpecified); + + string mikeFullDisclosureString = "{\"Name\": \"Mike Person\", \"NumberOfChildren\": \"0\"}"; + mike = JsonConvert.DeserializeObject(mikeFullDisclosureString); + + Console.WriteLine("mikeString specifies number of children: {0}", mike.NumberOfChildrenSpecified); + } + public class DictionaryKey { public string Value { get; set; } diff --git a/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs b/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs index 1e6b60b4..f906ebb4 100644 --- a/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs +++ b/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs @@ -361,7 +361,8 @@ namespace Newtonsoft.Json.Schema { bool optional = property.NullValueHandling == NullValueHandling.Ignore || property.DefaultValueHandling == DefaultValueHandling.Ignore || - property.ShouldSerialize != null; + property.ShouldSerialize != null || + property.GetIsSpecified != null; JsonSchema propertySchema = GenerateInternal(property.PropertyType, property.Required, optional); diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index edc91df4..26325934 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -35,6 +35,7 @@ using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Security.Permissions; +using System.Xml.Serialization; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Utilities; using Newtonsoft.Json.Linq; @@ -717,6 +718,8 @@ namespace Newtonsoft.Json.Serialization property.ShouldSerialize = CreateShouldSerializeTest(member); + SetIsSpecifiedActions(property, member); + return property; } @@ -733,6 +736,25 @@ namespace Newtonsoft.Json.Serialization return o => (bool) shouldSerializeCall(o); } + private void SetIsSpecifiedActions(JsonProperty property, MemberInfo member) + { + MemberInfo specifiedMember = member.DeclaringType.GetProperty(member.Name + JsonTypeReflector.SpecifiedPostfix); + if (specifiedMember == null) + specifiedMember = member.DeclaringType.GetField(member.Name + JsonTypeReflector.SpecifiedPostfix); + + if (specifiedMember == null || ReflectionUtils.GetMemberUnderlyingType(specifiedMember) != typeof(bool) + || (ReflectionUtils.GetAttribute(specifiedMember) != null + && ReflectionUtils.GetAttribute(specifiedMember) != null)) + { + return; + } + + Func specifiedPropertyGet = JsonTypeReflector.ReflectionDelegateFactory.CreateGet(specifiedMember); + + property.GetIsSpecified = o => (bool)specifiedPropertyGet(o); + property.SetIsSpecified = JsonTypeReflector.ReflectionDelegateFactory.CreateSet(specifiedMember); + } + /// /// Resolves the name of the property. /// diff --git a/Src/Newtonsoft.Json/Serialization/JsonProperty.cs b/Src/Newtonsoft.Json/Serialization/JsonProperty.cs index 13d721cd..0b2512e0 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonProperty.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonProperty.cs @@ -137,6 +137,18 @@ namespace Newtonsoft.Json.Serialization /// A predicate used to determine whether the property should be serialize. public Predicate ShouldSerialize { get; set; } + /// + /// Gets or sets a predicate used to determine whether the property should be serialized. + /// + /// A predicate used to determine whether the property should be serialized. + public Predicate GetIsSpecified { get; set; } + + /// + /// Gets or sets an action used to set whether the property has been deserialized. + /// + /// An action used to set whether the property has been deserialized. + public Action SetIsSpecified { get; set; } + /// /// Returns a that represents this instance. /// diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index ca80b5d8..8ca39544 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -552,7 +552,12 @@ namespace Newtonsoft.Json.Serialization // this could happen because of a JsonConverter against the type if ((!useExistingValue || value != currentValue) && ShouldSetPropertyValue(property, value)) + { property.ValueProvider.SetValue(target, value); + + if (property.SetIsSpecified != null) + property.SetIsSpecified(target, true); + } } private bool ShouldSetPropertyValue(JsonProperty property, object value) diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index 526d94b0..5b0e75d0 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -57,7 +57,8 @@ namespace Newtonsoft.Json.Serialization } } - public JsonSerializerInternalWriter(JsonSerializer serializer) : base(serializer) + public JsonSerializerInternalWriter(JsonSerializer serializer) + : base(serializer) { } @@ -109,7 +110,7 @@ namespace Newtonsoft.Json.Serialization } else if (valueContract is JsonStringContract) { - SerializeString(writer, value, (JsonStringContract) valueContract); + SerializeString(writer, value, (JsonStringContract)valueContract); } else if (valueContract is JsonObjectContract) { @@ -117,12 +118,12 @@ namespace Newtonsoft.Json.Serialization } else if (valueContract is JsonDictionaryContract) { - JsonDictionaryContract dictionaryContract = (JsonDictionaryContract) valueContract; + JsonDictionaryContract dictionaryContract = (JsonDictionaryContract)valueContract; SerializeDictionary(writer, dictionaryContract.CreateWrapper(value), dictionaryContract, member, collectionValueContract); } else if (valueContract is JsonArrayContract) { - JsonArrayContract arrayContract = (JsonArrayContract) valueContract; + JsonArrayContract arrayContract = (JsonArrayContract)valueContract; SerializeList(writer, arrayContract.CreateWrapper(value), arrayContract, member, collectionValueContract); } else if (valueContract is JsonLinqContract) @@ -132,13 +133,13 @@ namespace Newtonsoft.Json.Serialization #if !SILVERLIGHT && !PocketPC else if (valueContract is JsonISerializableContract) { - SerializeISerializable(writer, (ISerializable) value, (JsonISerializableContract) valueContract); + SerializeISerializable(writer, (ISerializable)value, (JsonISerializableContract)valueContract); } #endif #if !(NET35 || NET20 || SILVERLIGHT) else if (valueContract is JsonDynamicContract) { - SerializeDynamic(writer, (IDynamicMetaObjectProvider) value, (JsonDynamicContract) valueContract); + SerializeDynamic(writer, (IDynamicMetaObjectProvider)value, (JsonDynamicContract)valueContract); } #endif } @@ -242,9 +243,9 @@ namespace Newtonsoft.Json.Serialization // use the objectType's TypeConverter if it has one and can convert to a string if (converter != null #if !SILVERLIGHT - && !(converter is ComponentConverter) + && !(converter is ComponentConverter) #endif - && converter.GetType() != typeof(TypeConverter)) + && converter.GetType() != typeof(TypeConverter)) { if (converter.CanConvertTo(typeof(string))) { @@ -311,7 +312,7 @@ namespace Newtonsoft.Json.Serialization { try { - if (!property.Ignored && property.Readable && ShouldSerialize(property, value)) + if (!property.Ignored && property.Readable && ShouldSerialize(property, value) && IsSpecified(property, value)) { object memberValue = property.ValueProvider.GetValue(value); JsonContract memberContract = GetContractSafe(memberValue); @@ -610,5 +611,13 @@ namespace Newtonsoft.Json.Serialization return property.ShouldSerialize(target); } + + private bool IsSpecified(JsonProperty property, object target) + { + if (property.GetIsSpecified == null) + return true; + + return property.GetIsSpecified(target); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs index e1d72611..b9f64932 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs @@ -49,6 +49,7 @@ namespace Newtonsoft.Json.Serialization public const string ArrayValuesPropertyName = "$values"; public const string ShouldSerializePrefix = "ShouldSerialize"; + public const string SpecifiedPostfix = "Specified"; private static readonly ThreadSafeStore JsonConverterTypeCache = new ThreadSafeStore(GetJsonConverterTypeFromAttribute); #if !SILVERLIGHT && !PocketPC && !NET20