Izak Joubert
Izak Joubert

Reputation: 1001

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.

Upvotes: 1

Views: 4625

Answers (1)

Brian Rogers
Brian Rogers

Reputation: 129787

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

Upvotes: 1

Related Questions