Saturday, September 26, 2015

How to only Serialize Interface Properties with Json.NET

When you serialize an object with Newtonsoft's Json.NET it will resolve the serialization contract for the type being serialized. This means that if you want to serialize an object so that it matches one of the interfaces that it implements you will need to use a customized contract resolver.

When I first tried to do this I made a completely custom JsonConverter for the type that looked up the properties via reflection and just wrote their values out manually. Unfortunately had the side effect of bypassing all of the features the Newtonsoft provides with regard to decorating classes and customizing the serialization process for that object.

There was a good topic on Stack Overflow about this that led me to the custom contract resolver solution. However the sample implementation there is hard coded to only try to serialize one hard coded type for all serialization.

Below is an implementation (with tests) that allows you to specify a list of interfaces that you want to serialize by, and then if the object being serialized does implement that interface it will fall back on it's default contract.

InterfaceContractResolver Implementation

public class InterfaceContractResolver : DefaultContractResolver
{
    private readonly Type[] _interfaceTypes;
 
    private readonly ConcurrentDictionary<Type, Type> _typeToSerializeMap;
 
    public InterfaceContractResolver(params Type[] interfaceTypes)
    {
        _interfaceTypes = interfaceTypes;
 
        _typeToSerializeMap = new ConcurrentDictionary<Type, Type>();
    }
 
    protected override IList<JsonProperty> CreateProperties(
        Type type,
        MemberSerialization memberSerialization)
    {
        var typeToSerialize = _typeToSerializeMap.GetOrAdd(
            type,
            t => _interfaceTypes.FirstOrDefault(
                it => it.IsAssignableFrom(t)) ?? t);
 
        return base.CreateProperties(typeToSerialize, memberSerialization);
    }
}

Unit Tests

public interface ISample
{
    int Id { get; }
    IList<string> Names { get; }
}
 
public class Sample : ISample
{
    int ISample.Id
    {
        get { return Identity; }
    }
 
    public int Identity { get; set; }
    public IList<string> Names { get; set; }
    public bool IsValid { get; set; }
}
 
public class Example
{
    public int Id { get; set; }
    public string Names { get; set; }
    public bool IsTrue { get; set; }
}
 
public class InterfaceContractResolverTests
{
    [Fact]
    public void SerializeSample()
    {
        var settings = new JsonSerializerSettings
        {
            ContractResolver = new InterfaceContractResolver(typeof (ISample))
        };
 
        var sample = new Sample
        {
            Identity = 42,
            IsValid = true,
            Names = new[] {"Linq", "Sql"}
        };
 
        var sampleJson1 = JsonConvert.SerializeObject(sample);
        var sampleJson2 = JsonConvert.SerializeObject(sample, settings);
 
        Assert.Equal(
            @"{""Identity"":42,""Names"":[""Linq"",""Sql""],""IsValid"":true}",
            sampleJson1);
 
        Assert.Equal(
            @"{""Id"":42,""Names"":[""Linq"",""Sql""]}",
            sampleJson2);
    }
 
    [Fact]
    public void SerializeExample()
    {
        var settings = new JsonSerializerSettings
        {
            ContractResolver = new InterfaceContractResolver(typeof (ISample))
        };
 
        var example = new Example
        {
            Id = 2,
            Names = "Hello World",
            IsTrue = false
        };
 
        var exampleJson1 = JsonConvert.SerializeObject(example);
        var exampleJson2 = JsonConvert.SerializeObject(example, settings);
 
        Assert.Equal(
            @"{""Id"":2,""Names"":""Hello World"",""IsTrue"":false}",
            exampleJson1);
 
        Assert.Equal(exampleJson1, exampleJson2);
    }
}

Enjoy,
Tom

13 comments:

  1. This solution doesn't seem to work, at least with the current version of NewtonSoft.Json. I've found out that the properties in the interface are being ignored by default. If you replace the last line of CreateProperties with this, it solves the issue:

    var props = base.CreateProperties(typeToSerialize, memberSerialization);
    foreach(var prop in props)
    {
    prop.Ignored = false;
    }
    return props;

    ReplyDelete
    Replies
    1. Thanks for letting me know. I would like to go back and update this for .NET Core; if I do, then I will make sure to post a link here to the updated version.

      Delete
  2. Importance of community and the suffering economy becoming front of mind for many people today, the local market offers a place for residents to contribute directly to individuals and organizations for whom local production is paramount. The number one priority of a local market is to offer things that are homemade, handmade and homegrown. whitley residences

    ReplyDelete
  3. There are also strict laws in place to protect buyers and investors. A country which places emphasis on protecting investors will always attract investments. For property investment, there are heavy restrictions to protect people's money in the Singapore property market. nim collection

    ReplyDelete
  4. I was surfing net and fortunately came across this site and found very interesting stuff here. Its really fun to read. I enjoyed a lot. Thanks for sharing this wonderful information. el gouna white villas for sale

    ReplyDelete
  5. Building monetary models is a workmanship. The best way to further develop your specialty is to construct an assortment of monetary models across various businesses. We should attempt a model for a venture that isn't past the range of most people - a speculation property.
    Stump Removal Stockton

    ReplyDelete
  6. We should likewise pull the claimed property estimation line from the property tab (denoting the qualities in green to show that they come from an alternate sheet). nova city developers islamabad

    ReplyDelete
  7. The cutting of the storage compartment into the few areas that would then be able to be brought down with a controlled crane activity is additionally significant for the tight situation that might be available. Tree Trimming team Syracuse

    ReplyDelete
  8. So, can a simple layman person operate on a surgical table? No, right? An inexperienced and uncertified person cannot just use an axe or chainsaw to cut a tree. Albuquerque Tree Pruning

    ReplyDelete
  9. A great deal of American administration organizations get their payoffs from the help merchants who are continually conveyed to the properties. plot for sale in university town islamabad

    ReplyDelete
  10. Your expense of conveyance will likely be at least 5 pennies for each flyer under a specific conveyance sum (say 30,000).point loma homes for sale

    ReplyDelete
  11. Tajarat strives to be Pakistan's biggest real estate developer ever, guaranteeing the highest international standards, prompt execution, and lifetime customer loyalty. For further detail visit lahore smart city payment plan

    ReplyDelete

Real Time Web Analytics