Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to serialize all but a specific property in one specific serialization

I have an object that looks something like this (obviously simplified)

public class Person {
  public string Name { get; set; }
  public int Age { get; set; }
  public string ETag { get { return ... } }
}

What I would like is for ETag to be a hash of the json serialization of the object omitting the ETag property (to prevent a recursive loop). However, I cannot just use a [JsonIgnore] attribute since at other times I want to be able to json serialize the entire thing.

So what I want is something like this

public string ETag { get {
   return Hash(JsonConvert.Serialize(this, p => p.Ignore(x=>x.ETag) ));
}}

which is unfortunately not an API that exists. How would I achieve something similar?

like image 816
George Mauer Avatar asked Sep 30 '14 22:09

George Mauer


1 Answers

You can use a custom IContractResolver to programmatically ignore properties on an object. So I think the approach I would take is to create a simple resolver that can specifically ignore a single property on a single type (obviously you could extend this if needed), then make a helper method that can serialize using that resolver. Use the helper method from within your ETag property and you're good to go.

Here's the code for the resolver:

class IgnorePropertyResolver : DefaultContractResolver
{
    Type targetType;
    string targetPropertyName;

    public IgnorePropertyResolver(Type targetType, string propertyName)
    {
        this.targetType = targetType;
        this.targetPropertyName = propertyName;
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
        if (targetType == type)
        {
            props = props.Where(p => p.PropertyName != targetPropertyName).ToList();
        }
        return props;
    }
}

Here's the helper method (I also threw a Hash helper method in there since you had not defined it in your question):

static class JsonHelper
{
    public static string Serialize(object target, string propertyToIgnore)
    {
        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.ContractResolver = new IgnorePropertyResolver(target.GetType(), propertyToIgnore);
        return JsonConvert.SerializeObject(target, settings);
    }

    public static string Hash(string json)
    {
        using (var sha = new SHA1Managed())
        {
            return Convert.ToBase64String(sha.ComputeHash(Encoding.UTF8.GetBytes(json)));
        }
    }
}

And finally, here's a working demo:

class Program
{
    static void Main(string[] args)
    {
        Person p = new Person { Name = "Joe", Age = 26 };
        Console.WriteLine("Etag = " + p.ETag);
        Console.WriteLine();
        Console.WriteLine(JsonConvert.SerializeObject(p, Formatting.Indented));
    }
}

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string ETag
    {
        get { return JsonHelper.Hash(JsonHelper.Serialize(this, "ETag")); }
    }
}

Output:

Etag = T99YVDlrbZ66YL2u5MYjyIyO4Qk=

{
  "Name": "Joe",
  "Age": 26,
  "ETag": "T99YVDlrbZ66YL2u5MYjyIyO4Qk="
}

Fiddle: https://dotnetfiddle.net/YgVJ4K

like image 166
Brian Rogers Avatar answered Oct 19 '22 06:10

Brian Rogers