JavaScriptSerializer.Deserialize - how to change field names

Anthony picture Anthony · Jul 8, 2009 · Viewed 101.5k times · Source

Summary: How do I map a field name in JSON data to a field name of a .Net object when using JavaScriptSerializer.Deserialize ?

Longer version: I have the following JSON data coming to me from a server API (Not coded in .Net)

{"user_id":1234, "detail_level":"low"}

I have the following C# object for it:

[Serializable]
public class DataObject
{
    [XmlElement("user_id")]
    public int UserId { get; set; }

    [XmlElement("detail_level")]
    public DetailLevel DetailLevel { get; set; }
}

Where DetailLevel is an enum with "Low" as one of the values.

This test fails:

[TestMethod]
public void DataObjectSimpleParseTest()
{
    JavaScriptSerializer serializer = new JavaScriptSerializer();
    DataObject dataObject = serializer.Deserialize<DataObject>(JsonData);

    Assert.IsNotNull(dataObject);
    Assert.AreEqual(DetailLevel.Low, dataObject.DetailLevel);
    Assert.AreEqual(1234, dataObject.UserId);
}

And the last two asserts fail, since there is no data in those fields. If I change the JSON data to

 {"userid":1234, "detaillevel":"low"}

Then it passes. But I can't change the server's behaviour, and I want the client classes to have well-named properties in the C# idiom. I can't use LINQ to JSON since I want it to work outside of Silverlight. It looks like the XmlElement tags are having no effect. I don't know where I got the idea they were relevant at all, they probably aren't.

How do you do field name mapping in JavaScriptSerializer? Can it be done at all?

Answer

Anthony picture Anthony · Jul 10, 2009

I took another try at it, using the DataContractJsonSerializer class. This solves it:

The code looks like this:

using System.Runtime.Serialization;

[DataContract]
public class DataObject
{
    [DataMember(Name = "user_id")]
    public int UserId { get; set; }

    [DataMember(Name = "detail_level")]
    public string DetailLevel { get; set; }
}

And the test is:

using System.Runtime.Serialization.Json;

[TestMethod]
public void DataObjectSimpleParseTest()
{
        DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(DataObject));

        MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(JsonData));
        DataObject dataObject = serializer.ReadObject(ms) as DataObject;

        Assert.IsNotNull(dataObject);
        Assert.AreEqual("low", dataObject.DetailLevel);
        Assert.AreEqual(1234, dataObject.UserId);
}

The only drawback is that I had to change DetailLevel from an enum to a string - if you keep the enum type in place, the DataContractJsonSerializer expects to read a numeric value and fails. See DataContractJsonSerializer and Enums for further details.

In my opinion this is quite poor, especially as JavaScriptSerializer handles it correctly. This is the exception that you get trying to parse a string into an enum:

System.Runtime.Serialization.SerializationException: There was an error deserializing the object of type DataObject. The value 'low' cannot be parsed as the type 'Int64'. --->
System.Xml.XmlException: The value 'low' cannot be parsed as the type 'Int64'. --->  
System.FormatException: Input string was not in a correct format

And marking up the enum like this does not change this behaviour:

[DataContract]
public enum DetailLevel
{
    [EnumMember(Value = "low")]
    Low,
   ...
 }

This also seems to work in Silverlight.