The Perils of ASP.NET's JSON Serialiser

Posted by Tom on 2010-03-08 23:53

Promit Roy's weblog is on my feedreader (checkout his open source .NET profiler, by the way - it's a great piece of work), and his recent post about serialising interfaces reminded me of something I was going to write about ages ago and forgot about. A cautionary tale . . .

Picture the scene. We've got some users of several different kinds, all of which implement a common interface.

public interface IUser
{
    string Username
    {
        get;
    }
}
 
public class User : IUser
{
    public string Name { get; set; }
 
    public string Username
    {
        get { return Name; }
    }
 
    public User()
    {
        Name = "foo";
    }
}
 
public class OtherUser : IUser
{
    public string Username { get; set; }
    public string Password { get; set; }
    public string EmailAddress { get; set; }
 
    public OtherUser()
    {
        Username = "bar";
        Password = "password";
        EmailAddress = "a@a.com";
    }
}

Say we've got a web service returning an IUser in JSON format. You might expect to see something like this come out of the client side:

{
    'Name' : "foo"
    'Username' : "foo"
}

Looks good. But wait - Name isn't a property on the IUser interface . . .

Whereas the .NET XML serialiser will throw a wobbler if you ask it to serialise an object referenced through an interface due to its slavish adherence to the only-serialise-what-you-can-deserialise philosophy, the JSON serialiser will happily GetType on the object and then serialise based on that. That's harmless enough for objects of class User, but what happens if an OtherUser manages to sneak out of the web service under the same interface?

{
    'Username' : "bar"
    'Password' : "password"
    'EmailAddress' : "a@a.com"
}

Ruh roh.

This may be a trivial example, but it's an easy mistake to make given a more involved system (in our case a list of comments was being returned from a web service, and each comment had an IUser property that was being filled by a factory based on the session context). The lesson to be learned? Re-wrap any objects before you send them out of your web service.

public class SafeUser
{
    public string Username
    {
        get;
        private set;
    }
 
    public SafeUser(IUser user)
    {
        Username = user.Username;
    }
}