-
-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Serializer will now ignore: indexer properties, and properties with their name listed in a JsonIgnore above the declaring class #299
base: main
Are you sure you want to change the base?
Changes from all commits
9c5c5c6
51f3c9b
7bc39ab
049612c
453f180
e3f541c
37207a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
{ | ||
/// <summary> | ||
/// Used to test the JsonIgnore attribute. | ||
/// </summary> | ||
[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); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Used to 1-to-1 compare with JsonIgnoreTestClass. | ||
/// </summary> | ||
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 static JsonIgnoreTestClassNoAttr CreateTestClass() | ||
{ | ||
return new JsonIgnoreTestClassNoAttr() | ||
{ | ||
TestProperty = 1, | ||
OtherTestProperty = 2, | ||
AThirdTestProperty = 3 | ||
}; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1384,6 +1384,34 @@ public void DeserializeObjectWithStringContainingNonAsciiChars() | |
Assert.AreEqual(input.Value, result.Value); | ||
} | ||
|
||
[TestMethod] | ||
public void DeserializeObjectWithJsonIgnoreAttribute() | ||
{ | ||
OutputHelper.WriteLine("Starting JsonIgnore Test..."); | ||
Json.Configuration.Settings.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 | ||
icy3141 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
bool jsonSuccess = testObject.IsEqual(dserResult); | ||
Assert.IsTrue(jsonSuccess); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note that you can as well add an error message in case it's not successful. So you know where it broke and maybe other values you want to track in the test result. |
||
OutputHelper.WriteLine("Serialization/Deserialization was " + (jsonSuccess ? "" : "NOT ") + "successful."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you arrive here, then it's always successful. So no need to test the value of jsonSuccess There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think so, as long as my caching solution is acceptable. I will get to those last few fixes you suggested as soon as I can. The one thing I didn't do is run the performance tests with the code before I touched it. I can do that as well and provide my findings when I do my next push. |
||
|
||
//test ignored properties are actually ignored | ||
bool areIgnoredPropsPresent = jsonString.Contains("MyIgnoredProperty") | ||
|| jsonString.Contains("AnotherIgnoredProperty"); | ||
Assert.IsFalse(areIgnoredPropsPresent); | ||
OutputHelper.WriteLine("Ignore was " + (areIgnoredPropsPresent ? "NOT " : "") + "successful."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here, the value will always be false |
||
|
||
Json.Configuration.Settings.UseIgnoreAttribute = false; | ||
OutputHelper.WriteLine("UseIgnoreAttribute set back to false."); | ||
OutputHelper.WriteLine("Finished JsonIgnore Test."); | ||
} | ||
} | ||
|
||
#region Test classes | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Copyright (c) .NET Foundation and Contributors | ||
// See LICENSE file in the project root for full license information. | ||
|
||
using System; | ||
|
||
namespace nanoFramework.Json | ||
{ | ||
/// <summary> | ||
/// Hides properties from the json serializer. | ||
/// </summary> | ||
[System.AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] | ||
public sealed class JsonIgnoreAttribute : Attribute | ||
{ | ||
/// <summary> | ||
/// Array of property names for json serializer to ignore. | ||
/// </summary> | ||
public string[] PropertyNames { get; set; } | ||
|
||
/// <summary> | ||
/// Hides properties from the json serializer. | ||
/// </summary> | ||
/// <param name="getterNamesToIgnore">A comma separated list of property names to ignore in json.</param> | ||
public JsonIgnoreAttribute(string getterNamesToIgnore) | ||
{ | ||
// Split by commas, then trim whitespace for each | ||
PropertyNames = getterNamesToIgnore.Split(','); | ||
for(int i = 0; i < PropertyNames.Length; i++) | ||
{ | ||
PropertyNames[i] = PropertyNames[i].Trim(); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -77,26 +77,38 @@ 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 (Settings.UseIgnoreAttribute) | ||
{ | ||
classAttributes = type.GetCustomAttributes(false); | ||
} | ||
|
||
Hashtable hashtable = new(); | ||
|
||
// Iterate through all of the methods, looking for internal GET properties | ||
MethodInfo[] methods = type.GetMethods(); | ||
|
||
foreach (MethodInfo method in methods) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One thing here: BTW @josesimoes does nanoFramework support attributes on properties? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @torbacz there is nothing preventing you from decorating properties with attributes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that was my question 😅 Because then we could just decorate each property. Also I'm not quite sure, if we should implement such solution. It's not resolving any problems. If you have class with properties which you don't want to send, just create derived class and pass it to JSON lib. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's wait for the benchmark results and then decide. As a side comment: I keep being surprised on how much interest this library has gathered from the community! And the investments on improving it are also surprising to me. 😄 |
||
{ | ||
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) | ||
/// <summary> | ||
/// Checks whether a property (MethodInfo) should be serialized. | ||
/// </summary> | ||
/// <param name="method">The MethodInfo to check.</param> | ||
/// <param name="classAttributes">The cached class-level attributes. Only used if UseIgnoreAttribute is true.</param> | ||
private static bool ShouldSerializeMethod(MethodInfo method, object[] classAttributes) | ||
{ | ||
// We care only about property getters when serializing | ||
if (!method.Name.StartsWith("get_")) | ||
|
@@ -125,9 +137,71 @@ private static bool ShouldSerializeMethod(MethodInfo method) | |
return false; | ||
} | ||
|
||
// 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 && | ||
ShouldIgnorePropertyFromClassAttribute(method, classAttributes)) | ||
{ | ||
return false; | ||
icy3141 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
return true; | ||
} | ||
|
||
/// <summary> | ||
/// Checks for JsonIgnore attribute on a method's declaring class. Helper method for SerializeClass. | ||
/// </summary> | ||
/// <param name="method">The MethodInfo of a property getter to check.</param> | ||
/// <param name="classAttributes">The cached class-level attributes. Only used if UseIgnoreAttribute is true.</param> | ||
/// <returns></returns> | ||
private static bool ShouldIgnorePropertyFromClassAttribute(MethodInfo method, object[] classAttributes) | ||
{ | ||
string[] gettersToIgnore = null; | ||
|
||
foreach (object attribute in classAttributes) | ||
{ | ||
if (attribute is JsonIgnoreAttribute ignoreAttribute) | ||
{ | ||
gettersToIgnore = ignoreAttribute.PropertyNames; | ||
break; | ||
} | ||
} | ||
|
||
if (gettersToIgnore == null) | ||
{ | ||
return false; | ||
} | ||
|
||
foreach (string propertyName in gettersToIgnore) | ||
{ | ||
if (propertyName.Equals(ExtractGetterName(method))) | ||
{ | ||
return true; | ||
icy3141 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
return false; | ||
icy3141 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/// <summary> | ||
/// Extracts "get_" from MethodInfo.Name to retrieve the name of a getter property. | ||
/// Assumes the MethodInfo is for a getter, checked elsewhere. | ||
/// </summary> | ||
/// <param name="getterMethodInfo">The MethodInfo of the getter property.</param> | ||
/// <returns>The property name as it appears in written code.</returns> | ||
private static string ExtractGetterName(MethodInfo getterMethodInfo) | ||
{ | ||
// Substring(4) is to extract the "get_" for property methods | ||
return getterMethodInfo.Name.Substring(4); | ||
} | ||
|
||
/// <summary> | ||
/// Convert an IEnumerable to a JSON string. | ||
/// </summary> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add an extra line between the functions, so one is missing before the attribute. Same in the next few functions. It helps for readibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for more style errors. Even though I like organized code, I'm not used to checking that aspect quite so much. Plus it was a little messy from moving stuff around to get tests working. Will be better next time, don't want to waste your valuable time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing to apologize about! You're doing great! 👍🏻
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on José remark, you're doing great! No worry, you should have seen my very first PR on a .NET repository :-D Now, with habit, it's easy to spot them. We've been added StyleCop linter on the IoT repository as it's where we do have the most contributions. Not yet into those classes one. That will slowly come but it's not urgent. And anyway, we won't put it in place in the tests as it doesn't make too much sense.
So all good! You'll be indeed much better next time and at some point like us being able to spot those :-D
It's really to make it easier to read the code and navigate.