From 9c5c5c6b15fe27e73279714a7882a2365d3e63bf Mon Sep 17 00:00:00 2001 From: icy3141 Date: Thu, 16 Feb 2023 15:42:11 -0600 Subject: [PATCH 1/6] Added support for JsonIgnoreAttribute. Serializer will now ignore: indexer properties and properties with their name listed in a JsonIgnore above the declaring class. --- nanoFramework.Json/JsonIgnoreAttribute.cs | 63 ++++++++++++++++++++ nanoFramework.Json/JsonSerializer.cs | 47 ++++++++++++++- nanoFramework.Json/nanoFramework.Json.nfproj | 1 + 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 nanoFramework.Json/JsonIgnoreAttribute.cs diff --git a/nanoFramework.Json/JsonIgnoreAttribute.cs b/nanoFramework.Json/JsonIgnoreAttribute.cs new file mode 100644 index 00000000..b1bfc1ec --- /dev/null +++ b/nanoFramework.Json/JsonIgnoreAttribute.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections; +using System.Text; + +namespace nanoFramework.Json +{ + + + + + /// + /// Hides properties from the json serializer + /// + [System.AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] + public sealed class JsonIgnoreAttribute : Attribute + { + // See the attribute guidelines at + // http://go.microsoft.com/fwlink/?LinkId=85236 + + /// + /// Hides properties from the json serializer + /// + public JsonIgnoreAttribute() { + + PropertyNames = new string[0]; + } + /// + /// array of property names for json serializer to ignore + /// + public string[] PropertyNames { get; set; } + + /// + /// Hides properties from the json serializer + /// + /// + public JsonIgnoreAttribute(string getterNamesToIgnore) + { + PropertyNames = getterNamesToIgnore.Split(','); + for(int i = 0; i < PropertyNames.Length; i++) + { + PropertyNames[i] = PropertyNames[i].Trim(); + } + } + } + + //implementation to place above individual properties + ///// + ///// Hides a property from the json serializer + ///// + //[System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Method, + // Inherited = false, AllowMultiple = true)] + //public sealed class JsonIgnoreAttribute : Attribute + //{ + // // See the attribute guidelines at + // // http://go.microsoft.com/fwlink/?LinkId=85236 + + // /// + // /// Hides a property from the json serializer + // /// + // public JsonIgnoreAttribute() { } + //} + +} diff --git a/nanoFramework.Json/JsonSerializer.cs b/nanoFramework.Json/JsonSerializer.cs index e4591c20..9962cac7 100644 --- a/nanoFramework.Json/JsonSerializer.cs +++ b/nanoFramework.Json/JsonSerializer.cs @@ -125,7 +125,52 @@ private static bool ShouldSerializeMethod(MethodInfo method) return false; } - return true; + //Ignore property getters with at least one parameter (this[index] operator overloads) + ParameterInfo[] parameters = method.GetParameters(); + if (parameters.Length > 0) + { + return false; + } + parameters = null; + + // Ignore properties listed in [JsonIgnore()] attribute + if (ShouldIgnorePropertyFromClassAttribute(method)) + return false; + + // per property [JsonIgnore()] attribute - not working due to method.GetCustomAttributes returning empty + //var attributes = method.GetCustomAttributes(false); + //foreach (var attributeInfo in attributes) + //{ + // if(typeof(JsonIgnoreAttribute).IsInstanceOfType(attributeInfo)) + // { + // return false; + // } + //} + + return true; + } + + //split out method to check for ignore attribute + private static bool ShouldIgnorePropertyFromClassAttribute(MethodInfo method) + { + string[] gettersToIgnore = null; + object[] classAttributes = method.DeclaringType.GetCustomAttributes(true); + foreach (object attribute in classAttributes) + { + if (attribute is JsonIgnoreAttribute ignoreAttribute) + { + gettersToIgnore = ignoreAttribute.PropertyNames; + break; + } + } + classAttributes = null; + if (gettersToIgnore == null) return false; + foreach (string propertyName in gettersToIgnore) + { + if(propertyName.Equals(method.Name.Substring(4))) + return true; + } + return false; } /// diff --git a/nanoFramework.Json/nanoFramework.Json.nfproj b/nanoFramework.Json/nanoFramework.Json.nfproj index 3bd576b9..2c18d786 100644 --- a/nanoFramework.Json/nanoFramework.Json.nfproj +++ b/nanoFramework.Json/nanoFramework.Json.nfproj @@ -52,6 +52,7 @@ + From 51f3c9ba351430ce83061e2610f66b74bd86f658 Mon Sep 17 00:00:00 2001 From: icy3141 Date: Thu, 16 Feb 2023 16:04:08 -0600 Subject: [PATCH 2/6] fixed comments and format from implementing JsonIgnore --- nanoFramework.Json/JsonIgnoreAttribute.cs | 3 ++- nanoFramework.Json/JsonSerializer.cs | 28 ++++++++++++----------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/nanoFramework.Json/JsonIgnoreAttribute.cs b/nanoFramework.Json/JsonIgnoreAttribute.cs index b1bfc1ec..1763f9ed 100644 --- a/nanoFramework.Json/JsonIgnoreAttribute.cs +++ b/nanoFramework.Json/JsonIgnoreAttribute.cs @@ -32,9 +32,10 @@ public JsonIgnoreAttribute() { /// /// Hides properties from the json serializer /// - /// + /// a comma separated list of property names to ignore in json public JsonIgnoreAttribute(string getterNamesToIgnore) { + //split by commas, then load array PropertyNames = getterNamesToIgnore.Split(','); for(int i = 0; i < PropertyNames.Length; i++) { diff --git a/nanoFramework.Json/JsonSerializer.cs b/nanoFramework.Json/JsonSerializer.cs index 9962cac7..89e301ab 100644 --- a/nanoFramework.Json/JsonSerializer.cs +++ b/nanoFramework.Json/JsonSerializer.cs @@ -137,20 +137,22 @@ private static bool ShouldSerializeMethod(MethodInfo method) if (ShouldIgnorePropertyFromClassAttribute(method)) return false; - // per property [JsonIgnore()] attribute - not working due to method.GetCustomAttributes returning empty - //var attributes = method.GetCustomAttributes(false); - //foreach (var attributeInfo in attributes) - //{ - // if(typeof(JsonIgnoreAttribute).IsInstanceOfType(attributeInfo)) - // { - // return false; - // } - //} - - return true; + // per property [JsonIgnore()] attribute - not working due to method.GetCustomAttributes returning empty + //var attributes = method.GetCustomAttributes(false); + //foreach (var attributeInfo in attributes) + //{ + // if(typeof(JsonIgnoreAttribute).IsInstanceOfType(attributeInfo)) + // { + // return false; + // } + //} + + return true; } - //split out method to check for ignore attribute + /// + /// split out method to check for ignore attribute + /// private static bool ShouldIgnorePropertyFromClassAttribute(MethodInfo method) { string[] gettersToIgnore = null; @@ -167,7 +169,7 @@ private static bool ShouldIgnorePropertyFromClassAttribute(MethodInfo method) if (gettersToIgnore == null) return false; foreach (string propertyName in gettersToIgnore) { - if(propertyName.Equals(method.Name.Substring(4))) + if (propertyName.Equals(method.Name.Substring(4))) return true; } return false; From 7bc39abe763ae31ac8cf2fb15063a9884b140362 Mon Sep 17 00:00:00 2001 From: icy3141 Date: Fri, 17 Feb 2023 18:47:48 -0600 Subject: [PATCH 3/6] implemented static bool setting for JsonIgnore, and caching for class-level attributes when setting is enabled, unit tests, and fixed coding style problems. --- .../JsonIgnoreTestClass.cs | 63 +++++++++++++ .../nanoFramework.Json.Test.Shared.projitems | 1 + nanoFramework.Json.Test/JsonUnitTests.cs | 28 ++++++ nanoFramework.Json/JsonIgnoreAttribute.cs | 45 ++-------- nanoFramework.Json/JsonSerializer.cs | 89 +++++++++++++------ 5 files changed, 163 insertions(+), 63 deletions(-) create mode 100644 nanoFramework.Json.Test.Shared/JsonIgnoreTestClass.cs diff --git a/nanoFramework.Json.Test.Shared/JsonIgnoreTestClass.cs b/nanoFramework.Json.Test.Shared/JsonIgnoreTestClass.cs new file mode 100644 index 00000000..b8f34d1a --- /dev/null +++ b/nanoFramework.Json.Test.Shared/JsonIgnoreTestClass.cs @@ -0,0 +1,63 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +namespace nanoFramework.Json.Test.Shared +{ + /// + /// Used to test the JsonIgnore attribute. + /// + [JsonIgnore("MyIgnoredProperty, AnotherIgnoredProperty")] + public class JsonIgnoreTestClass + { + public int TestProperty { get; set; } + public int OtherTestProperty { get; set; } + public int AThirdTestProperty { get; set; } + public int MyIgnoredProperty => TestProperty + 1; + public int AnotherIgnoredProperty => OtherTestProperty + 1; + + public int this[int index] => TestProperty + index; + + public static JsonIgnoreTestClass CreateTestClass() + { + return new JsonIgnoreTestClass() + { + TestProperty = 1, + OtherTestProperty = 2, + AThirdTestProperty = 3 + }; + } + + public bool IsEqual(JsonIgnoreTestClass otherInstance) + { + return (TestProperty == otherInstance.TestProperty + && OtherTestProperty == otherInstance.OtherTestProperty + && AThirdTestProperty == otherInstance.AThirdTestProperty); + } + } + + ///// + ///// Used to 1-to-1 compare with JsonIgnoreTestClass. + ///// + //public class JsonIgnoreTestClassNoIgnore + //{ + // public int TestProperty { get; set; } + // public int OtherTestProperty { get; set; } + // public int AThirdTestProperty { get; set; } + // public int MyIgnoredProperty => TestProperty + 1; + // public int AnotherIgnoredProperty => OtherTestProperty + 1; + + // public int this[int index] => TestProperty + index; + + // public static JsonIgnoreTestClassNoIgnore CreateTestClass() + // { + // return new JsonIgnoreTestClassNoIgnore() + // { + // TestProperty = 1, + // OtherTestProperty = 2, + // AThirdTestProperty = 3 + // }; + // } + //} +} \ No newline at end of file diff --git a/nanoFramework.Json.Test.Shared/nanoFramework.Json.Test.Shared.projitems b/nanoFramework.Json.Test.Shared/nanoFramework.Json.Test.Shared.projitems index 884026b0..0929649b 100644 --- a/nanoFramework.Json.Test.Shared/nanoFramework.Json.Test.Shared.projitems +++ b/nanoFramework.Json.Test.Shared/nanoFramework.Json.Test.Shared.projitems @@ -10,6 +10,7 @@ + diff --git a/nanoFramework.Json.Test/JsonUnitTests.cs b/nanoFramework.Json.Test/JsonUnitTests.cs index f5db869d..cc13fd2b 100644 --- a/nanoFramework.Json.Test/JsonUnitTests.cs +++ b/nanoFramework.Json.Test/JsonUnitTests.cs @@ -1384,6 +1384,34 @@ public void DeserializeObjectWithStringContainingNonAsciiChars() Assert.Equal(input.Value, result.Value); } + [TestMethod] + public void DeserializeObjectWithJsonIgnoreAttribute() + { + OutputHelper.WriteLine("Starting JsonIgnore Test..."); + JsonSerializer.UseIgnoreAttribute = true; + OutputHelper.WriteLine("UseIgnoreAttribute enabled."); + + var testObject = JsonIgnoreTestClass.CreateTestClass(); + var jsonString = JsonSerializer.SerializeObject(testObject); + OutputHelper.WriteLine("After serialize."); + JsonIgnoreTestClass dserResult = JsonConvert.DeserializeObject(jsonString, typeof(JsonIgnoreTestClass)) as JsonIgnoreTestClass; + OutputHelper.WriteLine("After deserialize."); + + //test serialize and deserialize + bool jsonSuccess = testObject.IsEqual(dserResult); + Assert.IsTrue(jsonSuccess); + OutputHelper.WriteLine("Serialization/Deserialization was " + (jsonSuccess ? "" : "NOT ") + "successful."); + + //test ignored properties are actually ignored + bool areIgnoredPropsPresent = jsonString.Contains("MyIgnoredProperty") + || jsonString.Contains("AnotherIgnoredProperty"); + Assert.IsFalse(areIgnoredPropsPresent); + OutputHelper.WriteLine("Ignore was " + (areIgnoredPropsPresent ? "NOT " : "") + "successful."); + + JsonSerializer.UseIgnoreAttribute = false; + OutputHelper.WriteLine("UseIgnoreAttribute set back to false."); + OutputHelper.WriteLine("Finished JsonIgnore Test."); + } } #region Test classes diff --git a/nanoFramework.Json/JsonIgnoreAttribute.cs b/nanoFramework.Json/JsonIgnoreAttribute.cs index 1763f9ed..55409e48 100644 --- a/nanoFramework.Json/JsonIgnoreAttribute.cs +++ b/nanoFramework.Json/JsonIgnoreAttribute.cs @@ -1,41 +1,28 @@ -using System; -using System.Collections; -using System.Text; +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. + +using System; namespace nanoFramework.Json { - - - - /// /// Hides properties from the json serializer /// [System.AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] public sealed class JsonIgnoreAttribute : Attribute { - // See the attribute guidelines at - // http://go.microsoft.com/fwlink/?LinkId=85236 - - /// - /// Hides properties from the json serializer - /// - public JsonIgnoreAttribute() { - - PropertyNames = new string[0]; - } /// /// array of property names for json serializer to ignore /// public string[] PropertyNames { get; set; } /// - /// Hides properties from the json serializer + /// Hides properties from the json serializer. /// - /// a comma separated list of property names to ignore in json + /// A comma separated list of property names to ignore in json. public JsonIgnoreAttribute(string getterNamesToIgnore) { - //split by commas, then load array + // Split by commas, then trim whitespace for each PropertyNames = getterNamesToIgnore.Split(','); for(int i = 0; i < PropertyNames.Length; i++) { @@ -43,22 +30,4 @@ public JsonIgnoreAttribute(string getterNamesToIgnore) } } } - - //implementation to place above individual properties - ///// - ///// Hides a property from the json serializer - ///// - //[System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Method, - // Inherited = false, AllowMultiple = true)] - //public sealed class JsonIgnoreAttribute : Attribute - //{ - // // See the attribute guidelines at - // // http://go.microsoft.com/fwlink/?LinkId=85236 - - // /// - // /// Hides a property from the json serializer - // /// - // public JsonIgnoreAttribute() { } - //} - } diff --git a/nanoFramework.Json/JsonSerializer.cs b/nanoFramework.Json/JsonSerializer.cs index 89e301ab..6b010065 100644 --- a/nanoFramework.Json/JsonSerializer.cs +++ b/nanoFramework.Json/JsonSerializer.cs @@ -17,6 +17,16 @@ namespace nanoFramework.Json /// public class JsonSerializer { + private static bool _useIgnoreAttribute = false; + /// + /// If true, will check for JsonIgnoreAttribute upon serialization. Has a performance cost. + /// + public static bool UseIgnoreAttribute + { + get => _useIgnoreAttribute; + set => _useIgnoreAttribute = value; + } + JsonSerializer() { } @@ -77,6 +87,13 @@ public static string SerializeObject(object o, bool topObject = true) private static string SerializeClass(object o, Type type) { + // Cache the type's class-level attributes only if UseIgnoreAttribute setting is enabled. + object[] classAttributes = null; + if (UseIgnoreAttribute) + { + classAttributes = type.GetCustomAttributes(false); + } + Hashtable hashtable = new(); // Iterate through all of the methods, looking for internal GET properties @@ -84,19 +101,24 @@ private static string SerializeClass(object o, Type type) foreach (MethodInfo method in methods) { - if (!ShouldSerializeMethod(method)) + if (!ShouldSerializeMethod(method, classAttributes)) { continue; } - + object returnObject = method.Invoke(o, null); - hashtable.Add(method.Name.Substring(4), returnObject); + hashtable.Add(ExtractGetterName(method), returnObject); } return SerializeIDictionary(hashtable); } - private static bool ShouldSerializeMethod(MethodInfo method) + /// + /// Checks whether a property (MethodInfo) should be serialized. + /// + /// The MethodInfo to check. + /// The cached class-level attributes. Only used if UseIgnoreAttribute is true. + private static bool ShouldSerializeMethod(MethodInfo method, object[] classAttributes) { // We care only about property getters when serializing if (!method.Name.StartsWith("get_")) @@ -125,38 +147,36 @@ private static bool ShouldSerializeMethod(MethodInfo method) return false; } - //Ignore property getters with at least one parameter (this[index] operator overloads) + // Ignore property getters with at least one parameter (index properties / this[index] operator overloads) ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length > 0) { return false; } - parameters = null; - // Ignore properties listed in [JsonIgnore()] attribute - if (ShouldIgnorePropertyFromClassAttribute(method)) - return false; - - // per property [JsonIgnore()] attribute - not working due to method.GetCustomAttributes returning empty - //var attributes = method.GetCustomAttributes(false); - //foreach (var attributeInfo in attributes) - //{ - // if(typeof(JsonIgnoreAttribute).IsInstanceOfType(attributeInfo)) - // { - // return false; - // } - //} + // Only check for attribute if the setting is on + if (UseIgnoreAttribute) + { + // Ignore properties listed in [JsonIgnore()] attribute + if (ShouldIgnorePropertyFromClassAttribute(method, classAttributes)) + { + return false; + } + } return true; } /// - /// split out method to check for ignore attribute + /// Checks for JsonIgnore attribute on a method's declaring class. (helper method for SerializeClass) /// - private static bool ShouldIgnorePropertyFromClassAttribute(MethodInfo method) + /// The MethodInfo of a property getter to check. + /// The cached class-level attributes. Only used if UseIgnoreAttribute is true. + /// + private static bool ShouldIgnorePropertyFromClassAttribute(MethodInfo method, object[] classAttributes) { string[] gettersToIgnore = null; - object[] classAttributes = method.DeclaringType.GetCustomAttributes(true); + foreach (object attribute in classAttributes) { if (attribute is JsonIgnoreAttribute ignoreAttribute) @@ -165,16 +185,35 @@ private static bool ShouldIgnorePropertyFromClassAttribute(MethodInfo method) break; } } - classAttributes = null; - if (gettersToIgnore == null) return false; + + if (gettersToIgnore == null) + { + return false; + } + foreach (string propertyName in gettersToIgnore) { - if (propertyName.Equals(method.Name.Substring(4))) + if (propertyName.Equals(ExtractGetterName(method))) + { return true; + } } + return false; } + /// + /// Extracts "get_" from MethodInfo.Name to retrieve the name of a getter property. + /// Assumes the MethodInfo is for a getter, checked elsewhere. + /// + /// The MethodInfo of the getter property. + /// The property name as it appears in written code. + private static string ExtractGetterName(MethodInfo getterMethodInfo) + { + // Substring(4) is to extract the "get_" + return getterMethodInfo.Name.Substring(4); + } + /// /// Convert an IEnumerable to a JSON string. /// From 049612c3b57a6421f7c00f4ca9162440efa3c625 Mon Sep 17 00:00:00 2001 From: icy3141 Date: Fri, 17 Feb 2023 19:45:30 -0600 Subject: [PATCH 4/6] moved setting to Configuration.Settings, created Benchmark --- .../IgnoreSerializationBenchmark.cs | 64 +++++++++++++++++++ .../nanoFramework.Json.Benchmark.nfproj | 1 + nanoFramework.Json.Test/JsonUnitTests.cs | 4 +- nanoFramework.Json/Configuration/Settings.cs | 6 ++ nanoFramework.Json/JsonSerializer.cs | 14 +--- 5 files changed, 75 insertions(+), 14 deletions(-) create mode 100644 nanoFramework.Json.Benchmark/SerializationBenchmarks/IgnoreSerializationBenchmark.cs diff --git a/nanoFramework.Json.Benchmark/SerializationBenchmarks/IgnoreSerializationBenchmark.cs b/nanoFramework.Json.Benchmark/SerializationBenchmarks/IgnoreSerializationBenchmark.cs new file mode 100644 index 00000000..a194f428 --- /dev/null +++ b/nanoFramework.Json.Benchmark/SerializationBenchmarks/IgnoreSerializationBenchmark.cs @@ -0,0 +1,64 @@ +// +// Copyright (c) .NET Foundation and Contributors +// See LICENSE file in the project root for full license information. +// + +using nanoFramework.Benchmark; +using nanoFramework.Benchmark.Attributes; +using nanoFramework.Json.Benchmark.Base; +using nanoFramework.Json.Test.Shared; +using System; +using System.Collections; + +namespace nanoFramework.Json.Benchmark.SerializationBenchmarks +{ + [IterationCount(5)] + public class IgnoreSerializationBenchmark : BaseIterationBenchmark + { + private JsonIgnoreTestClass TestObject; + + [Setup] + public void Setup() + { + // Create a test object + TestObject = JsonIgnoreTestClass.CreateTestClass(); + } + + [Benchmark] + public void DoTest() + { + RunInIteration(() => + { + // Turn ON the UseIgnore setting + Configuration.Settings.UseIgnoreAttribute = true; + JsonConvert.SerializeObject(TestObject); + // Turn OFF the UseIgnore setting + Configuration.Settings.UseIgnoreAttribute = false; + }); + } + } + [IterationCount(5)] + public class NoIgnoreSerializationBenchmark : BaseIterationBenchmark + { + private JsonIgnoreTestClass TestObject; + + [Setup] + public void Setup() + { + // Create a test object + TestObject = JsonIgnoreTestClass.CreateTestClass(); + } + + [Benchmark] + public void DoTest() + { + RunInIteration(() => + { + // Turn OFF the UseIgnore setting + Configuration.Settings.UseIgnoreAttribute = false; + JsonConvert.SerializeObject(TestObject); + }); + } + } + +} diff --git a/nanoFramework.Json.Benchmark/nanoFramework.Json.Benchmark.nfproj b/nanoFramework.Json.Benchmark/nanoFramework.Json.Benchmark.nfproj index 4c400e32..fb330b4c 100644 --- a/nanoFramework.Json.Benchmark/nanoFramework.Json.Benchmark.nfproj +++ b/nanoFramework.Json.Benchmark/nanoFramework.Json.Benchmark.nfproj @@ -22,6 +22,7 @@ + diff --git a/nanoFramework.Json.Test/JsonUnitTests.cs b/nanoFramework.Json.Test/JsonUnitTests.cs index cc13fd2b..197e865e 100644 --- a/nanoFramework.Json.Test/JsonUnitTests.cs +++ b/nanoFramework.Json.Test/JsonUnitTests.cs @@ -1388,7 +1388,7 @@ public void DeserializeObjectWithStringContainingNonAsciiChars() public void DeserializeObjectWithJsonIgnoreAttribute() { OutputHelper.WriteLine("Starting JsonIgnore Test..."); - JsonSerializer.UseIgnoreAttribute = true; + Json.Configuration.Settings.UseIgnoreAttribute = true; OutputHelper.WriteLine("UseIgnoreAttribute enabled."); var testObject = JsonIgnoreTestClass.CreateTestClass(); @@ -1408,7 +1408,7 @@ public void DeserializeObjectWithJsonIgnoreAttribute() Assert.IsFalse(areIgnoredPropsPresent); OutputHelper.WriteLine("Ignore was " + (areIgnoredPropsPresent ? "NOT " : "") + "successful."); - JsonSerializer.UseIgnoreAttribute = false; + Json.Configuration.Settings.UseIgnoreAttribute = false; OutputHelper.WriteLine("UseIgnoreAttribute set back to false."); OutputHelper.WriteLine("Finished JsonIgnore Test."); } diff --git a/nanoFramework.Json/Configuration/Settings.cs b/nanoFramework.Json/Configuration/Settings.cs index adb0637f..8734693d 100644 --- a/nanoFramework.Json/Configuration/Settings.cs +++ b/nanoFramework.Json/Configuration/Settings.cs @@ -23,6 +23,12 @@ public static class Settings /// public static bool CaseSensitive { get; set; } = true; + + /// + /// If true, will check for JsonIgnoreAttribute upon serialization. Has a performance cost. Defaults to false. + /// + public static bool UseIgnoreAttribute { get; set; } = false; + /// /// Gets or sets a value indicating whether deserialization should throw exception when no property found. /// diff --git a/nanoFramework.Json/JsonSerializer.cs b/nanoFramework.Json/JsonSerializer.cs index 6b010065..0940468d 100644 --- a/nanoFramework.Json/JsonSerializer.cs +++ b/nanoFramework.Json/JsonSerializer.cs @@ -17,16 +17,6 @@ namespace nanoFramework.Json /// public class JsonSerializer { - private static bool _useIgnoreAttribute = false; - /// - /// If true, will check for JsonIgnoreAttribute upon serialization. Has a performance cost. - /// - public static bool UseIgnoreAttribute - { - get => _useIgnoreAttribute; - set => _useIgnoreAttribute = value; - } - JsonSerializer() { } @@ -89,7 +79,7 @@ private static string SerializeClass(object o, Type type) { // Cache the type's class-level attributes only if UseIgnoreAttribute setting is enabled. object[] classAttributes = null; - if (UseIgnoreAttribute) + if (Settings.UseIgnoreAttribute) { classAttributes = type.GetCustomAttributes(false); } @@ -155,7 +145,7 @@ private static bool ShouldSerializeMethod(MethodInfo method, object[] classAttri } // Only check for attribute if the setting is on - if (UseIgnoreAttribute) + if (Settings.UseIgnoreAttribute) { // Ignore properties listed in [JsonIgnore()] attribute if (ShouldIgnorePropertyFromClassAttribute(method, classAttributes)) From 453f180e13f1016c15b453644daa8f3b33d8e5a8 Mon Sep 17 00:00:00 2001 From: icy3141 Date: Sat, 18 Feb 2023 10:44:38 -0600 Subject: [PATCH 5/6] set up benchmarks for JsonIgnore setting. --- .../IgnoreSerializationBenchmark.cs | 64 ------------------- .../ReferenceTypesSerializationBenchmark.cs | 49 ++++++++++++++ .../nanoFramework.Json.Benchmark.nfproj | 1 - .../JsonIgnoreTestClass.cs | 42 ++++++------ nanoFramework.Json/Configuration/Settings.cs | 1 - nanoFramework.Json/JsonSerializer.cs | 18 +++--- 6 files changed, 78 insertions(+), 97 deletions(-) delete mode 100644 nanoFramework.Json.Benchmark/SerializationBenchmarks/IgnoreSerializationBenchmark.cs diff --git a/nanoFramework.Json.Benchmark/SerializationBenchmarks/IgnoreSerializationBenchmark.cs b/nanoFramework.Json.Benchmark/SerializationBenchmarks/IgnoreSerializationBenchmark.cs deleted file mode 100644 index a194f428..00000000 --- a/nanoFramework.Json.Benchmark/SerializationBenchmarks/IgnoreSerializationBenchmark.cs +++ /dev/null @@ -1,64 +0,0 @@ -// -// Copyright (c) .NET Foundation and Contributors -// See LICENSE file in the project root for full license information. -// - -using nanoFramework.Benchmark; -using nanoFramework.Benchmark.Attributes; -using nanoFramework.Json.Benchmark.Base; -using nanoFramework.Json.Test.Shared; -using System; -using System.Collections; - -namespace nanoFramework.Json.Benchmark.SerializationBenchmarks -{ - [IterationCount(5)] - public class IgnoreSerializationBenchmark : BaseIterationBenchmark - { - private JsonIgnoreTestClass TestObject; - - [Setup] - public void Setup() - { - // Create a test object - TestObject = JsonIgnoreTestClass.CreateTestClass(); - } - - [Benchmark] - public void DoTest() - { - RunInIteration(() => - { - // Turn ON the UseIgnore setting - Configuration.Settings.UseIgnoreAttribute = true; - JsonConvert.SerializeObject(TestObject); - // Turn OFF the UseIgnore setting - Configuration.Settings.UseIgnoreAttribute = false; - }); - } - } - [IterationCount(5)] - public class NoIgnoreSerializationBenchmark : BaseIterationBenchmark - { - private JsonIgnoreTestClass TestObject; - - [Setup] - public void Setup() - { - // Create a test object - TestObject = JsonIgnoreTestClass.CreateTestClass(); - } - - [Benchmark] - public void DoTest() - { - RunInIteration(() => - { - // Turn OFF the UseIgnore setting - Configuration.Settings.UseIgnoreAttribute = false; - JsonConvert.SerializeObject(TestObject); - }); - } - } - -} diff --git a/nanoFramework.Json.Benchmark/SerializationBenchmarks/ReferenceTypesSerializationBenchmark.cs b/nanoFramework.Json.Benchmark/SerializationBenchmarks/ReferenceTypesSerializationBenchmark.cs index df6ff952..a51b1383 100644 --- a/nanoFramework.Json.Benchmark/SerializationBenchmarks/ReferenceTypesSerializationBenchmark.cs +++ b/nanoFramework.Json.Benchmark/SerializationBenchmarks/ReferenceTypesSerializationBenchmark.cs @@ -23,6 +23,8 @@ public class ReferenceTypesSerializationBenchmark : BaseIterationBenchmark private JsonTestClassComplex complexClass; private JsonTestTown myTown; private ArrayList arrayList; + private JsonIgnoreTestClass ignoreTest; + private JsonIgnoreTestClassNoAttr ignoreTestNoAttr; [Setup] public void Setup() @@ -65,6 +67,8 @@ public void Setup() { DateTime.UtcNow }, { TimeSpan.FromSeconds(100) } }; + ignoreTest = JsonIgnoreTestClass.CreateTestClass(); + ignoreTestNoAttr = JsonIgnoreTestClassNoAttr.CreateTestClass(); } [Benchmark] @@ -120,5 +124,50 @@ public void ArrayList() JsonConvert.SerializeObject(arrayList); }); } + + [Benchmark] + public void ClassWithAttributeIgnoreEnabled() + { + RunInIteration(() => + { + // Turn OFF the UseIgnore setting + Configuration.Settings.UseIgnoreAttribute = true; + JsonConvert.SerializeObject(ignoreTest); + // Turn OFF the UseIgnore setting + Configuration.Settings.UseIgnoreAttribute = false; + }); + } + [Benchmark] + public void ClassWithAttributeIgnoreDisabled() + { + RunInIteration(() => + { + // Turn OFF the UseIgnore setting + Configuration.Settings.UseIgnoreAttribute = false; + JsonConvert.SerializeObject(ignoreTest); + }); + } + [Benchmark] + public void ClassNoAttributeIgnoreEnabled() + { + RunInIteration(() => + { + // Turn OFF the UseIgnore setting + Configuration.Settings.UseIgnoreAttribute = true; + JsonConvert.SerializeObject(ignoreTestNoAttr); + // Turn OFF the UseIgnore setting + Configuration.Settings.UseIgnoreAttribute = false; + }); + } + [Benchmark] + public void ClassNoAttributeIgnoreDisabled() + { + RunInIteration(() => + { + // Turn OFF the UseIgnore setting + Configuration.Settings.UseIgnoreAttribute = false; + JsonConvert.SerializeObject(ignoreTestNoAttr); + }); + } } } diff --git a/nanoFramework.Json.Benchmark/nanoFramework.Json.Benchmark.nfproj b/nanoFramework.Json.Benchmark/nanoFramework.Json.Benchmark.nfproj index fb330b4c..4c400e32 100644 --- a/nanoFramework.Json.Benchmark/nanoFramework.Json.Benchmark.nfproj +++ b/nanoFramework.Json.Benchmark/nanoFramework.Json.Benchmark.nfproj @@ -22,7 +22,6 @@ - diff --git a/nanoFramework.Json.Test.Shared/JsonIgnoreTestClass.cs b/nanoFramework.Json.Test.Shared/JsonIgnoreTestClass.cs index b8f34d1a..35be5c49 100644 --- a/nanoFramework.Json.Test.Shared/JsonIgnoreTestClass.cs +++ b/nanoFramework.Json.Test.Shared/JsonIgnoreTestClass.cs @@ -37,27 +37,27 @@ public bool IsEqual(JsonIgnoreTestClass otherInstance) } } - ///// - ///// Used to 1-to-1 compare with JsonIgnoreTestClass. - ///// - //public class JsonIgnoreTestClassNoIgnore - //{ - // public int TestProperty { get; set; } - // public int OtherTestProperty { get; set; } - // public int AThirdTestProperty { get; set; } - // public int MyIgnoredProperty => TestProperty + 1; - // public int AnotherIgnoredProperty => OtherTestProperty + 1; + /// + /// Used to 1-to-1 compare with JsonIgnoreTestClass. + /// + public class JsonIgnoreTestClassNoAttr + { + public int TestProperty { get; set; } + public int OtherTestProperty { get; set; } + public int AThirdTestProperty { get; set; } + public int MyIgnoredProperty => TestProperty + 1; + public int AnotherIgnoredProperty => OtherTestProperty + 1; - // public int this[int index] => TestProperty + index; + public int this[int index] => TestProperty + index; - // public static JsonIgnoreTestClassNoIgnore CreateTestClass() - // { - // return new JsonIgnoreTestClassNoIgnore() - // { - // TestProperty = 1, - // OtherTestProperty = 2, - // AThirdTestProperty = 3 - // }; - // } - //} + public static JsonIgnoreTestClassNoAttr CreateTestClass() + { + return new JsonIgnoreTestClassNoAttr() + { + TestProperty = 1, + OtherTestProperty = 2, + AThirdTestProperty = 3 + }; + } + } } \ No newline at end of file diff --git a/nanoFramework.Json/Configuration/Settings.cs b/nanoFramework.Json/Configuration/Settings.cs index 8734693d..35c16c94 100644 --- a/nanoFramework.Json/Configuration/Settings.cs +++ b/nanoFramework.Json/Configuration/Settings.cs @@ -23,7 +23,6 @@ public static class Settings /// public static bool CaseSensitive { get; set; } = true; - /// /// If true, will check for JsonIgnoreAttribute upon serialization. Has a performance cost. Defaults to false. /// diff --git a/nanoFramework.Json/JsonSerializer.cs b/nanoFramework.Json/JsonSerializer.cs index 0940468d..a3034850 100644 --- a/nanoFramework.Json/JsonSerializer.cs +++ b/nanoFramework.Json/JsonSerializer.cs @@ -137,21 +137,19 @@ private static bool ShouldSerializeMethod(MethodInfo method, object[] classAttri return false; } - // Ignore property getters with at least one parameter (index properties / this[index] operator overloads) - ParameterInfo[] parameters = method.GetParameters(); - if (parameters.Length > 0) + // Ignore indexer properties + // (string comparison is MUCH faster than method.GetParameters) + if (method.Name == "get_Item") { return false; } + // Ignore properties listed in [JsonIgnore()] attribute // Only check for attribute if the setting is on - if (Settings.UseIgnoreAttribute) + if (Settings.UseIgnoreAttribute && + ShouldIgnorePropertyFromClassAttribute(method, classAttributes)) { - // Ignore properties listed in [JsonIgnore()] attribute - if (ShouldIgnorePropertyFromClassAttribute(method, classAttributes)) - { - return false; - } + return false; } return true; @@ -200,7 +198,7 @@ private static bool ShouldIgnorePropertyFromClassAttribute(MethodInfo method, ob /// The property name as it appears in written code. private static string ExtractGetterName(MethodInfo getterMethodInfo) { - // Substring(4) is to extract the "get_" + // Substring(4) is to extract the "get_" for property methods return getterMethodInfo.Name.Substring(4); } From e3f541c4974bf19a36bf2cb455e5ed32df45d8f0 Mon Sep 17 00:00:00 2001 From: icy3141 <124085952+icy3141@users.noreply.github.com> Date: Mon, 20 Feb 2023 11:20:18 -0600 Subject: [PATCH 6/6] Apply suggestions from code review style changes from Ellerbach Co-authored-by: Laurent Ellerbach --- .../ReferenceTypesSerializationBenchmark.cs | 4 ++-- nanoFramework.Json/JsonIgnoreAttribute.cs | 4 ++-- nanoFramework.Json/JsonSerializer.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/nanoFramework.Json.Benchmark/SerializationBenchmarks/ReferenceTypesSerializationBenchmark.cs b/nanoFramework.Json.Benchmark/SerializationBenchmarks/ReferenceTypesSerializationBenchmark.cs index a51b1383..ccdfd728 100644 --- a/nanoFramework.Json.Benchmark/SerializationBenchmarks/ReferenceTypesSerializationBenchmark.cs +++ b/nanoFramework.Json.Benchmark/SerializationBenchmarks/ReferenceTypesSerializationBenchmark.cs @@ -130,7 +130,7 @@ public void ClassWithAttributeIgnoreEnabled() { RunInIteration(() => { - // Turn OFF the UseIgnore setting + // Turn ON the UseIgnore setting Configuration.Settings.UseIgnoreAttribute = true; JsonConvert.SerializeObject(ignoreTest); // Turn OFF the UseIgnore setting @@ -152,7 +152,7 @@ public void ClassNoAttributeIgnoreEnabled() { RunInIteration(() => { - // Turn OFF the UseIgnore setting + // Turn ON the UseIgnore setting Configuration.Settings.UseIgnoreAttribute = true; JsonConvert.SerializeObject(ignoreTestNoAttr); // Turn OFF the UseIgnore setting diff --git a/nanoFramework.Json/JsonIgnoreAttribute.cs b/nanoFramework.Json/JsonIgnoreAttribute.cs index 55409e48..b5f5c471 100644 --- a/nanoFramework.Json/JsonIgnoreAttribute.cs +++ b/nanoFramework.Json/JsonIgnoreAttribute.cs @@ -6,13 +6,13 @@ namespace nanoFramework.Json { /// - /// Hides properties from the json serializer + /// Hides properties from the json serializer. /// [System.AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] public sealed class JsonIgnoreAttribute : Attribute { /// - /// array of property names for json serializer to ignore + /// Array of property names for json serializer to ignore. /// public string[] PropertyNames { get; set; } diff --git a/nanoFramework.Json/JsonSerializer.cs b/nanoFramework.Json/JsonSerializer.cs index a3034850..640b9550 100644 --- a/nanoFramework.Json/JsonSerializer.cs +++ b/nanoFramework.Json/JsonSerializer.cs @@ -156,7 +156,7 @@ private static bool ShouldSerializeMethod(MethodInfo method, object[] classAttri } /// - /// Checks for JsonIgnore attribute on a method's declaring class. (helper method for SerializeClass) + /// Checks for JsonIgnore attribute on a method's declaring class. Helper method for SerializeClass. /// /// The MethodInfo of a property getter to check. /// The cached class-level attributes. Only used if UseIgnoreAttribute is true.