From 408a407105b5cee654caebc0ef2d80f29f2f6990 Mon Sep 17 00:00:00 2001 From: JamesNK Date: Sat, 20 Mar 2010 09:16:24 +0000 Subject: [PATCH] -Added support for ShouldSerialize conditional methods -Fixed not correctly skipping ignored JSON properties when deserializing via a non-default constructor --- .../Serialization/JsonSerializerTest.cs | 69 +++++++++++++++++++ .../Schema/JsonSchemaGenerator.cs | 3 +- .../Serialization/DefaultContractResolver.cs | 19 ++++- .../Serialization/JsonArrayContract.cs | 8 +-- .../Serialization/JsonDictionaryContract.cs | 4 +- .../Serialization/JsonProperty.cs | 2 + .../JsonSerializerInternalReader.cs | 4 +- .../JsonSerializerInternalWriter.cs | 10 ++- .../Serialization/JsonTypeReflector.cs | 2 + .../DynamicReflectionDelegateFactory.cs | 8 +-- .../LateBoundReflectionDelegateFactory.cs | 8 +-- Src/Newtonsoft.Json/Utilities/MethodCall.cs | 2 +- .../Utilities/ReflectionDelegateFactory.cs | 4 +- 13 files changed, 120 insertions(+), 23 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 48939998..a2362ab7 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -3247,5 +3247,74 @@ keyword such as type of business."" var json = JsonConvert.SerializeObject(child); JsonConvert.DeserializeObject>(json); } + + public class ConstructorCompexIgnoredProperty + { + [JsonIgnore] + public Product Ignored { get; set; } + public string First { get; set; } + public int Second { get; set; } + + public ConstructorCompexIgnoredProperty(string first, int second) + { + First = first; + Second = second; + } + } + + [Test] + public void DeserializeIgnoredPropertyInConstructor() + { + string json = @"{""First"":""First"",""Second"":2,""Ignored"":{""Name"":""James""},""AdditionalContent"":{""LOL"":true}}"; + + ConstructorCompexIgnoredProperty cc = JsonConvert.DeserializeObject(json); + Assert.AreEqual("First", cc.First); + Assert.AreEqual(2, cc.Second); + Assert.AreEqual(null, cc.Ignored); + } + + public class ShouldSerializeTestClass + { + internal bool _shouldSerializeName; + + public string Name { get; set; } + public int Age { get; set; } + + public void ShouldSerializeAge() + { + // dummy. should never be used because it doesn't return bool + } + + public bool ShouldSerializeName() + { + return _shouldSerializeName; + } + } + + [Test] + public void ShouldSerializeTest() + { + ShouldSerializeTestClass c = new ShouldSerializeTestClass(); + c.Name = "James"; + c.Age = 27; + + string json = JsonConvert.SerializeObject(c, Formatting.Indented); + + Assert.AreEqual(@"{ + ""Age"": 27 +}", json); + + c._shouldSerializeName = true; + json = JsonConvert.SerializeObject(c, Formatting.Indented); + + Assert.AreEqual(@"{ + ""Name"": ""James"", + ""Age"": 27 +}", json); + + ShouldSerializeTestClass deserialized = JsonConvert.DeserializeObject(json); + Assert.AreEqual("James", deserialized.Name); + Assert.AreEqual(27, deserialized.Age); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs b/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs index 805bccfb..1e6b60b4 100644 --- a/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs +++ b/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs @@ -360,7 +360,8 @@ namespace Newtonsoft.Json.Schema if (!property.Ignored) { bool optional = property.NullValueHandling == NullValueHandling.Ignore || - property.DefaultValueHandling == DefaultValueHandling.Ignore; + property.DefaultValueHandling == DefaultValueHandling.Ignore || + property.ShouldSerialize != 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 472b7bb9..06868e5d 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -284,7 +284,7 @@ namespace Newtonsoft.Json.Serialization private Func GetDefaultCreator(Type createdType) { - return JsonTypeReflector.ReflectionDelegateFactory.CreateDefaultConstructor(createdType); + return JsonTypeReflector.ReflectionDelegateFactory.CreateDefaultConstructor(createdType); } private void InitializeContract(JsonContract contract) @@ -419,7 +419,7 @@ namespace Newtonsoft.Json.Serialization ConstructorInfo constructorInfo = objectType.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new [] {typeof (SerializationInfo), typeof (StreamingContext)}, null); if (constructorInfo != null) { - MethodCall methodCall = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(constructorInfo); + MethodCall methodCall = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(constructorInfo); contract.ISerializableCreator = (args => methodCall(null, args)); } @@ -670,9 +670,24 @@ namespace Newtonsoft.Json.Serialization property.ObjectCreationHandling = (propertyAttribute != null) ? propertyAttribute._objectCreationHandling : null; property.IsReference = (propertyAttribute != null) ? propertyAttribute._isReference : null; + property.ShouldSerialize = CreateShouldSerializeTest(member); + return property; } + private Predicate CreateShouldSerializeTest(MemberInfo member) + { + MethodInfo shouldSerializeMethod = member.DeclaringType.GetMethod(JsonTypeReflector.ShouldSerializePrefix + member.Name, new Type[0]); + + if (shouldSerializeMethod == null || shouldSerializeMethod.ReturnType != typeof(bool)) + return null; + + MethodCall shouldSerializeCall = + JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(shouldSerializeMethod); + + return o => (bool) shouldSerializeCall(o); + } + /// /// Resolves the name of the property. /// diff --git a/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs b/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs index 19545822..e615edac 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs @@ -38,10 +38,10 @@ namespace Newtonsoft.Json.Serialization { internal Type CollectionItemType { get; private set; } - private bool _isCollectionItemTypeNullableType; - private Type _genericCollectionDefinitionType; + private readonly bool _isCollectionItemTypeNullableType; + private readonly Type _genericCollectionDefinitionType; private Type _genericWrapperType; - private MethodCall _genericWrapperCreator; + private MethodCall _genericWrapperCreator; /// /// Initializes a new instance of the class. @@ -78,7 +78,7 @@ namespace Newtonsoft.Json.Serialization _genericWrapperType = ReflectionUtils.MakeGenericType(typeof(CollectionWrapper<>), CollectionItemType); ConstructorInfo genericWrapperConstructor = _genericWrapperType.GetConstructor(new[] { _genericCollectionDefinitionType }); - _genericWrapperCreator = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(genericWrapperConstructor); + _genericWrapperCreator = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(genericWrapperConstructor); } return (IWrappedCollection)_genericWrapperCreator(null, list); diff --git a/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs b/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs index 03fdbe3b..4913b8cd 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs @@ -41,7 +41,7 @@ namespace Newtonsoft.Json.Serialization private readonly Type _genericCollectionDefinitionType; private Type _genericWrapperType; - private MethodCall _genericWrapperCreator; + private MethodCall _genericWrapperCreator; /// /// Initializes a new instance of the class. @@ -81,7 +81,7 @@ namespace Newtonsoft.Json.Serialization _genericWrapperType = ReflectionUtils.MakeGenericType(typeof(DictionaryWrapper<,>), DictionaryKeyType, DictionaryValueType); ConstructorInfo genericWrapperConstructor = _genericWrapperType.GetConstructor(new[] { _genericCollectionDefinitionType }); - _genericWrapperCreator = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(genericWrapperConstructor); + _genericWrapperCreator = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(genericWrapperConstructor); } return (IWrappedDictionary)_genericWrapperCreator(null, dictionary); diff --git a/Src/Newtonsoft.Json/Serialization/JsonProperty.cs b/Src/Newtonsoft.Json/Serialization/JsonProperty.cs index c1518985..f7732344 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonProperty.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonProperty.cs @@ -125,6 +125,8 @@ namespace Newtonsoft.Json.Serialization /// The object creation handling. public ObjectCreationHandling? ObjectCreationHandling { get; set; } + public Predicate ShouldSerialize { 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 83da8400..1228bcce 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -753,9 +753,9 @@ namespace Newtonsoft.Json.Serialization if (property != null) { if (!property.Ignored) - { propertyValues[property] = CreateValueProperty(reader, property, null, true, null); - } + else + reader.Skip(); } else { diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index c01b72a3..94353a47 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -311,7 +311,7 @@ namespace Newtonsoft.Json.Serialization { try { - if (!property.Ignored && property.Readable) + if (!property.Ignored && property.Readable && ShouldSerialize(property, value)) { object memberValue = property.ValueProvider.GetValue(value); JsonContract memberContract = GetContractSafe(memberValue); @@ -547,5 +547,13 @@ namespace Newtonsoft.Json.Serialization writer.WriteEnd(); } } + + private bool ShouldSerialize(JsonProperty property, object target) + { + if (property.ShouldSerialize == null) + return true; + + return property.ShouldSerialize(target); + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs index 45263587..0482c70d 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs @@ -48,6 +48,8 @@ namespace Newtonsoft.Json.Serialization public const string TypePropertyName = "$type"; public const string ArrayValuesPropertyName = "$values"; + public const string ShouldSerializePrefix = "ShouldSerialize"; + private static readonly ThreadSafeStore JsonConverterTypeCache = new ThreadSafeStore(GetJsonConverterTypeFromAttribute); #if !SILVERLIGHT && !PocketPC && !NET20 private static readonly ThreadSafeStore AssociatedMetadataTypesCache = new ThreadSafeStore(GetAssociateMetadataTypeFromAttribute); diff --git a/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs b/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs index d1effde7..3bffe953 100644 --- a/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs +++ b/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs @@ -47,7 +47,7 @@ namespace Newtonsoft.Json.Utilities return dynamicMethod; } - public override MethodCall CreateMethodCall(MethodBase method) + public override MethodCall CreateMethodCall(MethodBase method) { DynamicMethod dynamicMethod = CreateDynamicMethod(method.ToString(), typeof(object), new[] { typeof(object), typeof(object[]) }, method.DeclaringType); ILGenerator generator = dynamicMethod.GetILGenerator(); @@ -94,10 +94,10 @@ namespace Newtonsoft.Json.Utilities generator.Return(); - return (MethodCall)dynamicMethod.CreateDelegate(typeof(MethodCall)); + return (MethodCall)dynamicMethod.CreateDelegate(typeof(MethodCall)); } - public override Func CreateDefaultConstructor(Type type) + public override Func CreateDefaultConstructor(Type type) { DynamicMethod dynamicMethod = CreateDynamicMethod("Create" + type.FullName, typeof(object), Type.EmptyTypes, type); dynamicMethod.InitLocals = true; @@ -123,7 +123,7 @@ namespace Newtonsoft.Json.Utilities generator.Return(); - return (Func)dynamicMethod.CreateDelegate(typeof(Func)); + return (Func)dynamicMethod.CreateDelegate(typeof(Func)); } public override Func CreateGet(PropertyInfo propertyInfo) diff --git a/Src/Newtonsoft.Json/Utilities/LateBoundReflectionDelegateFactory.cs b/Src/Newtonsoft.Json/Utilities/LateBoundReflectionDelegateFactory.cs index 9f964317..e112ab44 100644 --- a/Src/Newtonsoft.Json/Utilities/LateBoundReflectionDelegateFactory.cs +++ b/Src/Newtonsoft.Json/Utilities/LateBoundReflectionDelegateFactory.cs @@ -33,7 +33,7 @@ namespace Newtonsoft.Json.Utilities { public static readonly LateBoundReflectionDelegateFactory Instance = new LateBoundReflectionDelegateFactory(); - public override MethodCall CreateMethodCall(MethodBase method) + public override MethodCall CreateMethodCall(MethodBase method) { ConstructorInfo c = method as ConstructorInfo; if (c != null) @@ -42,14 +42,14 @@ namespace Newtonsoft.Json.Utilities return (o, a) => method.Invoke(o, a); } - public override Func CreateDefaultConstructor(Type type) + public override Func CreateDefaultConstructor(Type type) { if (type.IsValueType) - return () => ReflectionUtils.CreateInstance(type); + return () => (T)ReflectionUtils.CreateInstance(type); ConstructorInfo constructorInfo = ReflectionUtils.GetDefaultConstructor(type, true); - return () => constructorInfo.Invoke(null); + return () => (T)constructorInfo.Invoke(null); } public override Func CreateGet(PropertyInfo propertyInfo) diff --git a/Src/Newtonsoft.Json/Utilities/MethodCall.cs b/Src/Newtonsoft.Json/Utilities/MethodCall.cs index efbc8175..93abb87f 100644 --- a/Src/Newtonsoft.Json/Utilities/MethodCall.cs +++ b/Src/Newtonsoft.Json/Utilities/MethodCall.cs @@ -25,5 +25,5 @@ namespace Newtonsoft.Json.Utilities { - internal delegate object MethodCall(T target, params object[] args); + internal delegate TResult MethodCall(T target, params object[] args); } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Utilities/ReflectionDelegateFactory.cs b/Src/Newtonsoft.Json/Utilities/ReflectionDelegateFactory.cs index 29b8fd07..1fcb762e 100644 --- a/Src/Newtonsoft.Json/Utilities/ReflectionDelegateFactory.cs +++ b/Src/Newtonsoft.Json/Utilities/ReflectionDelegateFactory.cs @@ -57,8 +57,8 @@ namespace Newtonsoft.Json.Utilities throw new Exception("Could not create setter for {0}.".FormatWith(CultureInfo.InvariantCulture, memberInfo)); } - public abstract MethodCall CreateMethodCall(MethodBase method); - public abstract Func CreateDefaultConstructor(Type type); + public abstract MethodCall CreateMethodCall(MethodBase method); + public abstract Func CreateDefaultConstructor(Type type); public abstract Func CreateGet(PropertyInfo propertyInfo); public abstract Func CreateGet(FieldInfo fieldInfo); public abstract Action CreateSet(FieldInfo fieldInfo);