2

I want to implement a conditional version of [JsonIgnore] attribute in C#. I don't want to use ShouldSerializePropertyName due to it's dependency on hardcoded property name.

My API model inherits from the database model and I certainly want to ignore database Ids but I also want to ignore some other properties based on which features the customer payed for. I don't want to use ShouldSerialize technique because the other developers in my team could change the database property name and accidentally make visible what should not be visible.

I read if I can optionally turn off the JsonIgnore attribute at runtime

but it looks like the suggested technique turns off all JsonIgnore together. What I want to do is to have just some of them off based on some condition.

Is there a work around? Is there some attribute which could do that? If I need to write a custom attribute, could you show me how please? Thanks!

Marina Melin
  • 162
  • 5
  • 10
  • If you don't want to use a hardcoded method name, what *do* you want to use? A static method with an attribute-specified name? An instance method with an attribute-specified name? Or something entirely runtime that doesn't require adding Json.NET attributes to your type? Is [Conditional member serialization based on query parameter?](https://stackoverflow.com/q/29713847/3744182) what you need? – dbc Jun 28 '17 at 21:07

1 Answers1

4

This is an interesting problem. My answer borrows heavily from the link you provided, but checks for a custom attribute defining your "Premium Content" (things that the user paid for):

Like your link, I have defined a class Foo, which will be serialized. It contains a child object PremiumStuff, which contains things that should only be serialized if the user paid for them. I have marked this child object with a custom attribute PremiumContent, which is also defined in this code snippet. I then used a custom class that inherits from DefaultContractResolver just like the link did, but in this implementation, I am checking each property for our custom attribute, and running the code in the if block only if the property is marked as PremiumContent. This conditional code checks a static bool called AllowPremiumContent to see whether we are allowing the premium content to be serialized. If it is not allowed, then we are setting the Ignore flag to true:

class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }
    [JsonIgnore]
    public string AlternateName { get; set; }
    [PremiumContent]
    public PremiumStuff ExtraContent { get; set; }
}

class PremiumStuff
{
    public string ExtraInfo { get; set; }
    public string SecretInfo { get; set; }
}

class IncludePremiumContentAttributesResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
        foreach (var prop in props)
        {
            if (Attribute.IsDefined(type.GetProperty(prop.PropertyName), typeof(PremiumContent)))
            {
                //if the attribute is marked with [PremiumContent]

                if (PremiumContentRights.AllowPremiumContent == false)
                {
                    prop.Ignored = true;   // Ignore this if PremiumContentRights.AllowPremiumContent is set to false
                }
            }
        }
        return props;
    }
}

[System.AttributeUsage(System.AttributeTargets.All)]
public class PremiumContent : Attribute
{
}

public static class PremiumContentRights
{
    public static bool AllowPremiumContent = true;
}

Now, let's implement this and see what we get. Here is my test code:

PremiumContentRights.AllowPremiumContent = true;
Foo foo = new Foo()
{
    Id = 1,
    Name = "Hello",
    AlternateName = "World",
    ExtraContent = new PremiumStuff()
    {
        ExtraInfo = "For premium",
        SecretInfo = "users only."
    }
};

JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new IncludePremiumContentAttributesResolver();
settings.Formatting = Formatting.Indented;

string json = JsonConvert.SerializeObject(foo, settings);

Debug.WriteLine(json);

In the first line, PremiumContentRights.AllowPremiumContent is set to true, and here is the output:

{
  "Id": 1,
  "Name": "Hello",
  "ExtraContent": {
    "ExtraInfo": "For premium",
    "SecretInfo": "users only."
  }
}

If we set AllowPremiumContent to False and run the code again, here is the output:

{
  "Id": 1,
  "Name": "Hello"
}

As you can see, the ExtraContent property is included or ignored depending on the state of the AllowPremiumContent variable.

Dave Smash
  • 2,941
  • 1
  • 18
  • 38
  • 2
    You might consider using [`[ThreadStatic]` or `ThreadLocal`](https://stackoverflow.com/q/18333885) for your `AllowPremiumContent`. Asp.net, for instances, shares the same contract resolver across multiple server threads. – dbc Jun 29 '17 at 19:53