From 64da81d3864c0194fa5865aedb182c336a9f7227 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 30 Apr 2013 00:13:20 +1200 Subject: [PATCH] -Added JsonDataExtensionAttribute --- .../Properties/AssemblyInfo.cs | 2 +- .../Serialization/JsonSerializerTest.cs | 167 ++++++++++++++++++ .../JsonExtensionDataAttribute.cs | 12 ++ Src/Newtonsoft.Json/JsonSerializer.cs | 1 + .../Newtonsoft.Json.Net20.csproj | 1 + .../Newtonsoft.Json.Net35.csproj | 1 + .../Newtonsoft.Json.Net40.csproj | 1 + .../Newtonsoft.Json.Portable.csproj | 1 + .../Newtonsoft.Json.Portable40.csproj | 1 + Src/Newtonsoft.Json/Newtonsoft.Json.csproj | 1 + .../Properties/AssemblyInfo.cs | 2 +- .../Serialization/DefaultContractResolver.cs | 71 +++++++- .../Serialization/JsonContract.cs | 9 + .../Serialization/JsonObjectContract.cs | 5 + .../JsonSerializerInternalReader.cs | 54 ++++-- .../ExpressionReflectionDelegateFactory.cs | 8 +- .../Utilities/TypeExtensions.cs | 12 +- 17 files changed, 325 insertions(+), 24 deletions(-) create mode 100644 Src/Newtonsoft.Json/JsonExtensionDataAttribute.cs diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index 9d785d08..8ea14963 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -75,4 +75,4 @@ using System.Security; // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: [assembly: AssemblyVersion("4.5.0.0")] -[assembly: AssemblyFileVersion("5.0.4.16028")] +[assembly: AssemblyFileVersion("5.0.4.16030")] diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 416e832a..e85197e9 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -134,6 +134,173 @@ namespace Newtonsoft.Json.Tests.Serialization } + public class ExtensionDataTestClass + { + public string Name { get; set; } + [JsonProperty("custom_name")] + public string CustomName { get; set; } + [JsonIgnore] + public IList Ignored { get; set; } + public bool GetPrivate { get; internal set; } + public bool GetOnly + { + get { return true; } + } + public readonly string Readonly = "Readonly"; + public IList Ints { get; set; } + + [JsonExtensionData] + internal IDictionary ExtensionData { get; set; } + + public ExtensionDataTestClass() + { + Ints = new List { 0 }; + } + } + + [Test] + public void ExtensionDataTest() + { + string json = @"{ + ""Ints"": [1,2,3], + ""Ignored"": [1,2,3], + ""Readonly"": ""Readonly"", + ""Name"": ""Actually set!"", + ""CustomName"": ""Wrong name!"", + ""GetPrivate"": true, + ""GetOnly"": true, + ""NewValueSimple"": true, + ""NewValueComplex"": [1,2,3] +}"; + + ExtensionDataTestClass c = JsonConvert.DeserializeObject(json); + + Assert.AreEqual("Actually set!", c.Name); + Assert.AreEqual(4, c.Ints.Count); + + Assert.AreEqual("Readonly", (string)c.ExtensionData["Readonly"]); + Assert.AreEqual("Wrong name!", (string)c.ExtensionData["CustomName"]); + Assert.AreEqual(true, (bool)c.ExtensionData["GetPrivate"]); + Assert.AreEqual(true, (bool)c.ExtensionData["GetOnly"]); + Assert.AreEqual(true, (bool)c.ExtensionData["NewValueSimple"]); + Assert.IsTrue(JToken.DeepEquals(new JArray(1, 2, 3), c.ExtensionData["NewValueComplex"])); + Assert.IsTrue(JToken.DeepEquals(new JArray(1, 2, 3), c.ExtensionData["Ignored"])); + + Assert.AreEqual(7, c.ExtensionData.Count); + } + + public class MultipleExtensionDataAttributesTestClass + { + public string Name { get; set; } + [JsonExtensionData] + internal IDictionary ExtensionData1 { get; set; } + [JsonExtensionData] + internal IDictionary ExtensionData2 { get; set; } + } + + public class ExtensionDataAttributesInheritanceTestClass : MultipleExtensionDataAttributesTestClass + { + [JsonExtensionData] + internal IDictionary ExtensionData0 { get; set; } + } + + public class FieldExtensionDataAttributeTestClass + { + [JsonExtensionData] + internal IDictionary ExtensionData; + } + + public class PublicExtensionDataAttributeTestClass + { + public string Name { get; set; } + [JsonExtensionData] + public IDictionary ExtensionData; + } + + [Test] + public void SerializePublicExtensionData() + { + string json = JsonConvert.SerializeObject(new PublicExtensionDataAttributeTestClass + { + Name = "Name!", + ExtensionData = new Dictionary + { + { "Test", 1 } + } + }); + + Assert.AreEqual(@"{""Name"":""Name!""}", json); + } + + [Test] + public void DeserializePublicExtensionData() + { + string json = @"{ + 'Name':'Name!', + 'NoMatch':'NoMatch!', + 'ExtensionData':{'HAI':true} +}"; + + var c = JsonConvert.DeserializeObject(json); + + Assert.AreEqual("Name!", c.Name); + Assert.AreEqual(2, c.ExtensionData.Count); + + Assert.AreEqual("NoMatch!", (string)(JValue)c.ExtensionData["NoMatch"]); + + // the ExtensionData property is put into the extension data + // inception + var o = (JObject)c.ExtensionData["ExtensionData"]; + Assert.AreEqual(1, o.Count); + Assert.IsTrue(JToken.DeepEquals(new JObject { { "HAI", true } }, o)); + } + + [Test] + public void FieldExtensionDataAttributeTest_Serialize() + { + FieldExtensionDataAttributeTestClass c = new FieldExtensionDataAttributeTestClass + { + ExtensionData = new Dictionary() + }; + + string json = JsonConvert.SerializeObject(c); + + Assert.AreEqual("{}", json); + } + + [Test] + public void FieldExtensionDataAttributeTest_Deserialize() + { + var c = JsonConvert.DeserializeObject("{'first':1,'second':2}"); + + Assert.AreEqual(2, c.ExtensionData.Count); + Assert.AreEqual(1, (int)(JToken)c.ExtensionData["first"]); + Assert.AreEqual(2, (int)(JToken)c.ExtensionData["second"]); + } + + [Test] + public void MultipleExtensionDataAttributesTest() + { + var c = JsonConvert.DeserializeObject("{'first':1,'second':2}"); + + Assert.AreEqual(null, c.ExtensionData1); + Assert.AreEqual(2, c.ExtensionData2.Count); + Assert.AreEqual(1, (int)c.ExtensionData2["first"]); + Assert.AreEqual(2, (int)c.ExtensionData2["second"]); + } + + [Test] + public void ExtensionDataAttributesInheritanceTest() + { + var c = JsonConvert.DeserializeObject("{'first':1,'second':2}"); + + Assert.AreEqual(null, c.ExtensionData1); + Assert.AreEqual(null, c.ExtensionData2); + Assert.AreEqual(2, c.ExtensionData0.Count); + Assert.AreEqual(1, (int)c.ExtensionData0["first"]); + Assert.AreEqual(2, (int)c.ExtensionData0["second"]); + } + [Test] public void GenericCollectionInheritance() { diff --git a/Src/Newtonsoft.Json/JsonExtensionDataAttribute.cs b/Src/Newtonsoft.Json/JsonExtensionDataAttribute.cs new file mode 100644 index 00000000..c916418e --- /dev/null +++ b/Src/Newtonsoft.Json/JsonExtensionDataAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Newtonsoft.Json +{ + /// + /// Instructs the to populate properties with no matching class member onto the specified collection. + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] + public class JsonExtensionDataAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonSerializer.cs b/Src/Newtonsoft.Json/JsonSerializer.cs index 9be8acdf..277c31b1 100644 --- a/Src/Newtonsoft.Json/JsonSerializer.cs +++ b/Src/Newtonsoft.Json/JsonSerializer.cs @@ -484,6 +484,7 @@ namespace Newtonsoft.Json /// public static JsonSerializer CreateDefault() { + // copy static to local variable to avoid concurrency issues Func defaultSettingsCreator = JsonConvert.DefaultSettings; JsonSerializerSettings defaultSettings = (defaultSettingsCreator != null) ? defaultSettingsCreator() : null; diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj index 9b25fe98..901294ea 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj @@ -103,6 +103,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj index d68bcab8..703220c3 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net35.csproj @@ -114,6 +114,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj index 14e005fb..8371f371 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net40.csproj @@ -116,6 +116,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj index d71f7dc7..8ae0275a 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable.csproj @@ -87,6 +87,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj index ef3e2a85..b5744d64 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Portable40.csproj @@ -87,6 +87,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index f5a0f74d..304e1bc8 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -88,6 +88,7 @@ + diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index 05e1ac70..62fd6f7a 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -92,6 +92,6 @@ using System.Security; // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: [assembly: AssemblyVersion("4.5.0.0")] -[assembly: AssemblyFileVersion("5.0.4.16028")] +[assembly: AssemblyFileVersion("5.0.4.16030")] [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index f42ebf30..afe74451 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -389,9 +389,75 @@ namespace Newtonsoft.Json.Serialization contract.ConstructorParameters.AddRange(CreateConstructorParameters(constructor, contract.Properties)); } } + + contract.ExtensionDataSetter = GetExtensionDataForType(contract.NonNullableUnderlyingType); + return contract; } + private ExtensionDataSetter GetExtensionDataForType(Type type) + { + ExtensionDataSetter extensionDataSetter = null; + + foreach (Type baseType in GetClassHierarchyForType(type)) + { + IList members = new List(); + members.AddRange(baseType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + members.AddRange(baseType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + foreach (MemberInfo member in members) + { + MemberTypes memberType = member.MemberType(); + if (memberType != MemberTypes.Property && memberType != MemberTypes.Field) + continue; + + // last instance of attribute wins on type if there are multiple + if (!member.IsDefined(typeof(JsonExtensionDataAttribute), false)) + continue; + + Type t = ReflectionUtils.GetMemberUnderlyingType(member); + + Type dictionaryType; + if (ReflectionUtils.ImplementsGenericDefinition(t, typeof(IDictionary<,>), out dictionaryType)) + { + Type keyType = dictionaryType.GetGenericArguments()[0]; + Type valueType = dictionaryType.GetGenericArguments()[1]; + + // change type to a class if it is the base interface so it can be instantiated if needed + if (ReflectionUtils.IsGenericDefinition(t, typeof(IDictionary<,>))) + t = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); + + if (keyType.IsAssignableFrom(typeof(string)) && valueType.IsAssignableFrom(typeof(JToken))) + { + MethodInfo addMethod = t.GetMethod("Add", new [] { keyType, valueType }); + Func getExtensionDataDictionary = JsonTypeReflector.ReflectionDelegateFactory.CreateGet(member); + Action setExtensionDataDictionary = JsonTypeReflector.ReflectionDelegateFactory.CreateSet(member); + Func createExtensionDataDictionary = JsonTypeReflector.ReflectionDelegateFactory.CreateDefaultConstructor(t); + MethodCall setExtensionDataDictionaryValue = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(addMethod); + + extensionDataSetter = (o, key, value) => + { + object dictionary = getExtensionDataDictionary(o); + if (dictionary == null) + { + dictionary = createExtensionDataDictionary(); + setExtensionDataDictionary(o, dictionary); + } + + setExtensionDataDictionaryValue(dictionary, key, value); + }; + + continue; + } + } + + throw new JsonException("Invalid extension data attribute on '{0}'. Member '{1}' type must implement IDictionary.".FormatWith(CultureInfo.InvariantCulture, GetClrTypeFullName(member.DeclaringType), member.Name)); + } + } + + return extensionDataSetter; + } + private ConstructorInfo GetAttributeConstructor(Type objectType) { IList markedConstructors = objectType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Where(c => c.IsDefined(typeof(JsonConstructorAttribute), true)).ToList(); @@ -593,8 +659,7 @@ namespace Newtonsoft.Json.Serialization foreach (Type baseType in GetClassHierarchyForType(type)) { - // while we allow more than one OnSerialized total, only one can be defined - // per class + // while we allow more than one OnSerialized total, only one can be defined per class MethodInfo currentOnSerializing = null; MethodInfo currentOnSerialized = null; MethodInfo currentOnDeserializing = null; @@ -1020,6 +1085,8 @@ namespace Newtonsoft.Json.Serialization bool hasJsonIgnoreAttribute = JsonTypeReflector.GetAttribute(attributeProvider) != null + // automatically ignore extension data dictionary property if it is public + || JsonTypeReflector.GetAttribute(attributeProvider) != null #if !(SILVERLIGHT || NETFX_CORE || PORTABLE40 || PORTABLE) || JsonTypeReflector.GetAttribute(attributeProvider) != null #endif diff --git a/Src/Newtonsoft.Json/Serialization/JsonContract.cs b/Src/Newtonsoft.Json/Serialization/JsonContract.cs index 9d9597f6..9de15ed0 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonContract.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.Serialization; +using Newtonsoft.Json.Linq; using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json.Serialization @@ -63,6 +64,14 @@ namespace Newtonsoft.Json.Serialization /// The error context. public delegate void SerializationErrorCallback(object o, StreamingContext context, ErrorContext errorContext); + /// + /// Sets extension data for an object during deserialization. + /// + /// The object to set extension data on. + /// The extension data key. + /// The extension data value. + public delegate void ExtensionDataSetter(object o, string key, JToken value); + /// /// Contract details for a used by the . /// diff --git a/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs b/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs index 545e5d97..342f0e99 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs @@ -76,6 +76,11 @@ namespace Newtonsoft.Json.Serialization /// The parametrized constructor. public ConstructorInfo ParametrizedConstructor { get; set; } + /// + /// Gets or sets the extension data setter. + /// + public ExtensionDataSetter ExtensionDataSetter { get; set; } + private bool? _hasRequiredOrDefaultValueProperties; internal bool HasRequiredOrDefaultValueProperties { diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 558a9c98..5377daaf 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -719,7 +719,7 @@ To fix this error either change the JSON to a {1} or change the deserialized typ return value; } - private void SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, object target) + private bool SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, object target) { object currentValue; bool useExistingValue; @@ -727,7 +727,7 @@ To fix this error either change the JSON to a {1} or change the deserialized typ bool gottenCurrentValue; if (CalculatePropertyDetails(property, ref propertyConverter, containerContract, containerProperty, reader, target, out useExistingValue, out currentValue, out propertyContract, out gottenCurrentValue)) - return; + return false; object value; @@ -758,7 +758,12 @@ To fix this error either change the JSON to a {1} or change the deserialized typ property.SetIsSpecified(target, true); } + + return true; } + + // the value wasn't set be JSON was populated onto the existing value + return useExistingValue; } private bool CalculatePropertyDetails(JsonProperty property, ref JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, object target, out bool useExistingValue, out object currentValue, out JsonContract propertyContract, out bool gottenCurrentValue) @@ -769,10 +774,7 @@ To fix this error either change the JSON to a {1} or change the deserialized typ gottenCurrentValue = false; if (property.Ignored) - { - reader.Skip(); return true; - } JsonToken tokenType = reader.TokenType; @@ -798,26 +800,17 @@ To fix this error either change the JSON to a {1} or change the deserialized typ } if (!property.Writable && !useExistingValue) - { - reader.Skip(); return true; - } // test tokentype here because null might not be convertable to some types, e.g. ignoring null when applied to DateTime if (property.NullValueHandling.GetValueOrDefault(Serializer._nullValueHandling) == NullValueHandling.Ignore && tokenType == JsonToken.Null) - { - reader.Skip(); return true; - } // test tokentype here because default value might not be convertable to actual type, e.g. default of "" for DateTime if (HasFlag(property.DefaultValueHandling.GetValueOrDefault(Serializer._defaultValueHandling), DefaultValueHandling.Ignore) && JsonReader.IsPrimitiveToken(tokenType) && MiscellaneousUtils.ValueEquals(reader.Value, property.GetResolvedDefaultValue())) - { - reader.Skip(); return true; - } if (currentValue == null) { @@ -1367,7 +1360,8 @@ To fix this error either change the environment to be fully trusted, change the JsonConverter propertyConverter = GetConverter(property.PropertyContract, property.MemberConverter, null, null); - SetPropertyValue(property, propertyConverter, null, member, reader, newObject); + if (!SetPropertyValue(property, propertyConverter, null, member, reader, newObject)) + reader.Skip(); } else { @@ -1700,7 +1694,10 @@ To fix this error either change the environment to be fully trusted, change the if (Serializer._missingMemberHandling == MissingMemberHandling.Error) throw JsonSerializationException.Create(reader, "Could not find member '{0}' on object of type '{1}'".FormatWith(CultureInfo.InvariantCulture, memberName, contract.UnderlyingType.Name)); - reader.Skip(); + if (!reader.Read()) + break; + + SetExtensionData(contract, reader, memberName, newObject); continue; } @@ -1714,7 +1711,9 @@ To fix this error either change the environment to be fully trusted, change the SetPropertyPresence(reader, property, propertiesPresence); - SetPropertyValue(property, propertyConverter, contract, member, reader, newObject); + // set extension data if property is ignored or readonly + if (!SetPropertyValue(property, propertyConverter, contract, member, reader, newObject)) + SetExtensionData(contract, reader, memberName, newObject); } catch (Exception ex) { @@ -1745,6 +1744,27 @@ To fix this error either change the environment to be fully trusted, change the return newObject; } + private void SetExtensionData(JsonObjectContract contract, JsonReader reader, string memberName, object o) + { + if (contract.ExtensionDataSetter != null) + { + try + { + JToken extensionDataValue = JToken.ReadFrom(reader); + + contract.ExtensionDataSetter(o, memberName, extensionDataValue); + } + catch (Exception ex) + { + throw JsonSerializationException.Create(reader, "Error setting value in extension data for type '{0}'.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType), ex); + } + } + else + { + reader.Skip(); + } + } + private void EndObject(object newObject, JsonReader reader, JsonObjectContract contract, int initialDepth, Dictionary propertiesPresence) { if (propertiesPresence != null) diff --git a/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs b/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs index e8d4c570..6b584c07 100644 --- a/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs +++ b/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs @@ -80,7 +80,13 @@ namespace Newtonsoft.Json.Utilities } if (method is MethodInfo) - callExpression = EnsureCastExpression(callExpression, type); + { + MethodInfo m = (MethodInfo)method; + if (m.ReturnType != typeof(void)) + callExpression = EnsureCastExpression(callExpression, type); + else + callExpression = Expression.Block(callExpression, Expression.Constant(null)); + } LambdaExpression lambdaExpression = Expression.Lambda(typeof(MethodCall), callExpression, targetParameterExpression, argsParameterExpression); diff --git a/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs b/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs index b88b540c..4f4aa5c5 100644 --- a/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs +++ b/Src/Newtonsoft.Json/Utilities/TypeExtensions.cs @@ -348,7 +348,11 @@ namespace Newtonsoft.Json.Utilities public static IEnumerable GetProperties(this Type type, BindingFlags bindingFlags) { - return type.GetTypeInfo().GetPropertiesRecursive().Where(p => TestAccessibility(p, bindingFlags)); + IList properties = (bindingFlags.HasFlag(BindingFlags.DeclaredOnly)) + ? type.GetTypeInfo().DeclaredProperties.ToList() + : type.GetTypeInfo().GetPropertiesRecursive(); + + return properties.Where(p => TestAccessibility(p, bindingFlags)); } private static IList GetMembersRecursive(this TypeInfo type) @@ -424,7 +428,11 @@ namespace Newtonsoft.Json.Utilities public static IEnumerable GetFields(this Type type, BindingFlags bindingFlags) { - return type.GetTypeInfo().GetFieldsRecursive().Where(f => TestAccessibility(f, bindingFlags)).ToList(); + IList fields = (bindingFlags.HasFlag(BindingFlags.DeclaredOnly)) + ? type.GetTypeInfo().DeclaredFields.ToList() + : type.GetTypeInfo().GetFieldsRecursive(); + + return fields.Where(f => TestAccessibility(f, bindingFlags)).ToList(); } private static bool TestAccessibility(PropertyInfo member, BindingFlags bindingFlags)