1

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.

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
Izak Joubert
  • 906
  • 11
  • 29

1 Answers1

1

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

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • It is possible to add logic to the resolver such that it will only do its special behavior for one or more specific classes. This could be done in several ways-- you could hardcode the class name(s) in the resolver; you could pass the class name(s) in the constructor, or you could introduce another attribute which would be applied to the target class(es) to mark them. In any case, you would add a check in `CreateProperty` whether the name of the property's `DeclaringType` matches the target class name. Here is an example using the constructor approach: https://dotnetfiddle.net/XSJnzC – Brian Rogers Aug 09 '20 at 22:27