views:

408

answers:

4
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;

namespace ConsoleApplication1 {
  internal class Program {
    private static void Main(string[] args) {
      var pony = new Pony();
      var serializer = new DataContractJsonSerializer(pony.GetType());
      var example = @"{""Foo"":null}";
      var stream = new MemoryStream(Encoding.UTF8.GetBytes(example.ToCharArray()));
      stream.Position = 0;
      pony = (Pony) serializer.ReadObject(stream);
      // The previous line throws an exception.
    }
  }

  [DataContract]
  public class Pony {
    [DataMember]
    private int Foo { get; set; }
  }
}

Sometimes the serialization throws a casting error on Int32s being set to null. Is there any way to hook into the Json-serializer?

+1  A: 

IMHO the best thing would be to change the Foo type from Int32 to System.Nullable<Int32> as this would best reflect its semantics (if it can be null) but if you cannot modify this class or if using DataContractJsonSerializer is not an obligation for you, Json.NET has extension points that allow you to do this (it also happens to be better performing).

For example you could write a custom type converter:

internal class NullableIntConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(int);
    }

    public override object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer)
    {
        if (reader.Value == null)
        {
            return default(int);
        }
        return int.Parse(reader.Value.ToString());
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new System.NotImplementedException();
    }
}

that could be registered and used like this:

internal class Program
{
    private static void Main(string[] args)
    {
        var serializer = new JsonSerializer();
        serializer.Converters.Add(new NullableIntConverter());
        using (var reader = new StringReader(@"{""Foo"":null}"))
        using (var jsonReader = new JsonTextReader(reader))
        {
            var pony = serializer.Deserialize<Pony>(jsonReader);
            Console.WriteLine(pony.Foo);
        }
    }
}
Darin Dimitrov
It can't be null, or rather it shouldn't be null but sometimes it gets set to null on some clients, but I haven't been able to reproduce.Switching to JsonConverter might be an option, though it feels a bit unsafe since there might be subtle differences making things go bad. I was hoping for a similar solution using the DataContractJsonSerializer.Wasn't JsonConverter marked obsolete for a period?
svinto
In the latest version of Json.NET that I've used to test this it was not obsolete.
Darin Dimitrov
I checked this, it was marked as obsolete in .net 3.5 but was not marked in .net 3.5 sp1. I remember this obsolete marking being the reason for not using it in the first place.
svinto
A: 

The easiest way of making it work would be to set the int to be a nullable int instead. I've debugged through the example code and that seems to work fine.

[DataContract]
public class Pony
{
    [DataMember]
    private int? Foo { get; set; }
}
WestDiscGolf
It shouldn't be possible to insert a null value, therefor int? is not an option. I want to be able to handle these on a case-by-case basis or at least get info on what property is causing the error being thrown.
svinto
A: 

DataContractJsonSerializer has a DataContractSurrogate property that you can assign. Your surrogate class, which is an implementation of IDataContractSurrogate, can in its GetDeserializedObject do your custom handling of null values.

Jacob
Do you have an example on how to do this? I can't seem to figure it out. (I can just see GetDataContractType in the surrogate being called once with typeof(Pony) as the type passed in, dunno what to do with that.
svinto
+1  A: 

If you are using .NET 3.5 or later (and I hope you do), you can use JavaScriptSerializer instead. It's more "dynamic" (doesn't require [DataContract] and [DataMember] attributes) and I've never had any problems with it before. You just pick up any object you want, any type and serialize with it :)

In .net 3.5 it was part of the System.Web.Extensions.dll, but in .NET 4 this assembly is now part of the framework.

You can easily add your own custom Serializer code to it, and handle it manually. This way you'll get the control that you want, and you'll know which property misbehaves!

e.g:

void Main()
{
    var js = new JavaScriptSerializer();
    js.RegisterConverters(new[] { new PonySerializer() });
    var pony = js.Deserialize<Pony>(@"{""Foo"":""null""}");
    pony.Dump();
}

public class PonySerializer : JavaScriptConverter
{
    public override IEnumerable<Type> SupportedTypes
    {
        get { return new [] { typeof(Pony) }; }
    }

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        if (dictionary == null)
            throw new ArgumentNullException("dictionary");

        if (type == typeof(Pony))
        {
            var pony = new Pony();
            int intValue;

            if (!int.TryParse(dictionary["Foo"] as string, out intValue))
                intValue = -1; // default value. You can throw an exception or log it or do whatever you want here

            pony.Foo = intValue;
            return pony;
        }
        return null;
    }
}

public class Pony
{
    public int Foo { get; set; }
}

P.S. This example was written in LinqPad, hence the missing usings, parameters on Main() etc :P

Artiom Chilaru