Search code examples
c#json.netjson-serialization

Custom JSON contract resolver to ignore all properties without a custom annotation


I'm looking to combine [JsonProperty("name")] and ![JsonIgnore] into my own custom resolver and I just need some help on the syntax.

So when serializing this class I want to ignore all properties without my custom attribute and also specify the serialized name for the property like so:

public class MyClass
{
    [MyCustomProperty("name")]
    public string SomeName { get; set; }

    [MyCustomProperty("value")]
    public string SomeValue { get; set; }
    
    public string AnotherName {get; set; }
    
    public string AnotherValue {get; set; }
}

Expected result:

{
    "name": "Apple",
    "value": "Delicious"
}

This is how far I got with my resolver:

public class MyCustomProperty : Attribute
{
    public string Property { get; set; }
    public MyCustomProperty(string property)
    {
        Property = property;
    }
}

public class CustomResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
        Type itemType = property.PropertyType.GetGenericArguments().First();
        MyCustomProperty customProperty = itemType.GetCustomAttribute<MyCustomProperty>();
        property.PropertyName = MyCustomProperty.Property;
        return property;
    }
}

I'm not exactly sure where to add the ignore if no attribute part.


Solution

  • JsonProperty has an AttributeProvider on it which you can use to find the custom attributes on that property. I recommend you use that. So basically you would try to get the attribute, and if it exists, you set the name like you are doing, otherwise you set Ignored = true.

    As an aside, I would recommend you rename your MyCustomProperty class to MyCustomPropertyAttribute, in keeping with standard conventions for classes that derive from System.Attribute. (Don't worry, the [MyCustomProperty("name")] annotation need not change, as the Attribute part is optional in annotations.) You should also apply the [AttributeUsage] attribute to your custom attribute class to indicate how it is allowed to be used. Lastly, I recommend you rename Property to PropertyName to make it clear that it is a name (string) and not the property itself (e.g. PropertyInfo).

    So the code would look like this:

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public class MyCustomPropertyAttribute : Attribute
    {
        public string PropertyName { get; set; }
        public MyCustomPropertyAttribute(string propertyName)
        {
            PropertyName = propertyName;
        }
    }
    
    public class CustomResolver : DefaultContractResolver
    {
        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            JsonProperty property = base.CreateProperty(member, memberSerialization);
            MyCustomPropertyAttribute customAttribute = (MyCustomPropertyAttribute)property.AttributeProvider.GetAttributes(typeof(MyCustomPropertyAttribute), true).FirstOrDefault();
            if (customAttribute != null)
            {
                property.PropertyName = customAttribute.PropertyName;
            }
            else
            {
                property.Ignored = true;
            }
            return property;
        }
    }
    

    Working demo: https://dotnetfiddle.net/thQc0f


    All of that said, you don't actually need a custom resolver to get the behavior you want. You could simply apply a [JsonObject(MemberSerialization.OptIn)] attribute to MyClass and then use the normal [JsonProperty] attributes on those properties that you want to be included. Any properties not marked will then be ignored. (See Serialization Attributes in the Json.Net documentation.)

    [JsonObject(MemberSerialization.OptIn)]
    public class MyClass
    {
        [JsonProperty("name")]
        public string SomeName { get; set; }
    
        [MyCustomProperty("value")]
        public string SomeValue { get; set; }
        
        public string AnotherName {get; set; }
        
        public string AnotherValue {get; set; }
    }
    

    Demo: https://dotnetfiddle.net/qY6nGR