JavaScriptSerializer.Deserialize - how to change field names
Asked Answered
D

9

74

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?

Detonation answered 8/7, 2009 at 19:49 Comment(3)
I hate JavaScriptSerializer. JwtSecurityTokenHandler uses it through the static JsonExtensions.Serializer property, which means changing it during runtime could affect other code that expects it to be unchanged. Many of these classes are that way, unfortunately. :(Outvote
[XmlElement("user_id")] XmlElement does not exist, where did you get that ? post fiull namespace or import dll'sQuickstep
use [System.Xml.Serialization.XmlElement ("user_id")] <-- fullnamespaceQuickstep
D
74

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.

Detonation answered 8/7, 2009 at 19:49 Comment(2)
Great solution! With .Net 4.5 seems working fine for enum members with just plain [DataMember] declaration (no need for [EnumMember] etc.)Pearlypearman
What's in your JsonData? When I do this as you've written, I get a SerializationException to the effect that the Serializer is expecting a root element, as though it's expecting XML. My JSON data is {"user": "THEDOMAIN\\MDS", "password": "JJJJ"}Guck
M
20

By creating a custom JavaScriptConverter you can map any name to any property. But it does require hand coding the map, which is less than ideal.

public class DataObjectJavaScriptConverter : JavaScriptConverter
{
    private static readonly Type[] _supportedTypes = new[]
    {
        typeof( DataObject )
    };

    public override IEnumerable<Type> SupportedTypes 
    { 
        get { return _supportedTypes; } 
    }

    public override object Deserialize( IDictionary<string, object> dictionary, 
                                        Type type, 
                                        JavaScriptSerializer serializer )
    {
        if( type == typeof( DataObject ) )
        {
            var obj = new DataObject();
            if( dictionary.ContainsKey( "user_id" ) )
                obj.UserId = serializer.ConvertToType<int>( 
                                           dictionary["user_id"] );
            if( dictionary.ContainsKey( "detail_level" ) )
                obj.DetailLevel = serializer.ConvertToType<DetailLevel>(
                                           dictionary["detail_level"] );

            return obj;
        }

        return null;
    }

    public override IDictionary<string, object> Serialize( 
            object obj, 
            JavaScriptSerializer serializer )
    {
        var dataObj = obj as DataObject;
        if( dataObj != null )
        {
            return new Dictionary<string,object>
            {
                {"user_id", dataObj.UserId },
                {"detail_level", dataObj.DetailLevel }
            }
        }
        return new Dictionary<string, object>();
    }
}

Then you can deserialize like so:

var serializer = new JavaScriptSerializer();
serialzer.RegisterConverters( new[]{ new DataObjectJavaScriptConverter() } );
var dataObj = serializer.Deserialize<DataObject>( json );
Meek answered 8/7, 2009 at 19:49 Comment(0)
S
13

Json.NET will do what you want (disclaimer: I'm the author of the package). It supports reading DataContract/DataMember attributes as well as its own to change the property names. Also there is the StringEnumConverter class for serializing enum values as the name rather than the number.

Scarab answered 8/7, 2009 at 19:49 Comment(1)
A two-line code examples showing using the attribute would be nice to see in this answer.Flinty
J
12

There is no standard support for renaming properties in JavaScriptSerializer however you can quite easily add your own:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Script.Serialization;
using System.Reflection;

public class JsonConverter : JavaScriptConverter
{
    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        List<MemberInfo> members = new List<MemberInfo>();
        members.AddRange(type.GetFields());
        members.AddRange(type.GetProperties().Where(p => p.CanRead && p.CanWrite && p.GetIndexParameters().Length == 0));

        object obj = Activator.CreateInstance(type);

        foreach (MemberInfo member in members)
        {
            JsonPropertyAttribute jsonProperty = (JsonPropertyAttribute)Attribute.GetCustomAttribute(member, typeof(JsonPropertyAttribute));

            if (jsonProperty != null && dictionary.ContainsKey(jsonProperty.Name))
            {
                SetMemberValue(serializer, member, obj, dictionary[jsonProperty.Name]);
            }
            else if (dictionary.ContainsKey(member.Name))
            {
                SetMemberValue(serializer, member, obj, dictionary[member.Name]);
            }
            else
            {
                KeyValuePair<string, object> kvp = dictionary.FirstOrDefault(x => string.Equals(x.Key, member.Name, StringComparison.InvariantCultureIgnoreCase));

                if (!kvp.Equals(default(KeyValuePair<string, object>)))
                {
                    SetMemberValue(serializer, member, obj, kvp.Value);
                }
            }
        }

        return obj;
    }


    private void SetMemberValue(JavaScriptSerializer serializer, MemberInfo member, object obj, object value)
    {
        if (member is PropertyInfo)
        {
            PropertyInfo property = (PropertyInfo)member;                
            property.SetValue(obj, serializer.ConvertToType(value, property.PropertyType), null);
        }
        else if (member is FieldInfo)
        {
            FieldInfo field = (FieldInfo)member;
            field.SetValue(obj, serializer.ConvertToType(value, field.FieldType));
        }
    }


    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {
        Type type = obj.GetType();
        List<MemberInfo> members = new List<MemberInfo>();
        members.AddRange(type.GetFields());
        members.AddRange(type.GetProperties().Where(p => p.CanRead && p.CanWrite && p.GetIndexParameters().Length == 0));

        Dictionary<string, object> values = new Dictionary<string, object>();

        foreach (MemberInfo member in members)
        {
            JsonPropertyAttribute jsonProperty = (JsonPropertyAttribute)Attribute.GetCustomAttribute(member, typeof(JsonPropertyAttribute));

            if (jsonProperty != null)
            {
                values[jsonProperty.Name] = GetMemberValue(member, obj);
            }
            else
            {
                values[member.Name] = GetMemberValue(member, obj);
            }
        }

        return values;
    }

    private object GetMemberValue(MemberInfo member, object obj)
    {
        if (member is PropertyInfo)
        {
            PropertyInfo property = (PropertyInfo)member;
            return property.GetValue(obj, null);
        }
        else if (member is FieldInfo)
        {
            FieldInfo field = (FieldInfo)member;
            return field.GetValue(obj);
        }

        return null;
    }


    public override IEnumerable<Type> SupportedTypes
    {
        get 
        {
            return new[] { typeof(DataObject) };
        }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class JsonPropertyAttribute : Attribute
{
    public JsonPropertyAttribute(string name)
    {
        Name = name;
    }

    public string Name
    {
        get;
        set;
    }
}

The DataObject class then becomes:

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

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

I appreicate this might be a little late but thought other people wanting to use the JavaScriptSerializer rather than the DataContractJsonSerializer might appreciate it.

Jorry answered 8/7, 2009 at 19:49 Comment(1)
I've used your code with generics like JsonConverter<T> : JavaScriptConverter so this class can be used with any type.Farra
P
5

I have used the using Newtonsoft.Json as below. Create an object:

 public class WorklistSortColumn
  {
    [JsonProperty(PropertyName = "field")]
    public string Field { get; set; }

    [JsonProperty(PropertyName = "dir")]
    public string Direction { get; set; }

    [JsonIgnore]
    public string SortOrder { get; set; }
  }

Now Call the below method to serialize to Json object as shown below.

string sortColumn = JsonConvert.SerializeObject(worklistSortColumn);
Paramagnet answered 8/7, 2009 at 19:49 Comment(0)
S
5

Create a class inherited from JavaScriptConverter. You must then implement three things:

Methods-

  1. Serialize
  2. Deserialize

Property-

  1. SupportedTypes

You can use the JavaScriptConverter class when you need more control over the serialization and deserialization process.

JavaScriptSerializer serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[] { new MyCustomConverter() });

DataObject dataObject = serializer.Deserialize<DataObject>(JsonData);

Here is a link for further information

Shortcircuit answered 8/7, 2009 at 19:49 Comment(0)
S
3

For those who don't want to go for Newtonsoft Json.Net or DataContractJsonSerializer for some reason (I can't think of any :) ), here is an implementation of JavaScriptConverter that supports DataContract and enum to string conversion -

    public class DataContractJavaScriptConverter : JavaScriptConverter
    {
        private static readonly List<Type> _supportedTypes = new List<Type>();

        static DataContractJavaScriptConverter()
        {
            foreach (Type type in Assembly.GetExecutingAssembly().DefinedTypes)
            {
                if (Attribute.IsDefined(type, typeof(DataContractAttribute)))
                {
                    _supportedTypes.Add(type);
                }
            }
        }

        private bool ConvertEnumToString = false;

        public DataContractJavaScriptConverter() : this(false)
        {
        }

        public DataContractJavaScriptConverter(bool convertEnumToString)
        {
            ConvertEnumToString = convertEnumToString;
        }

        public override IEnumerable<Type> SupportedTypes
        {
            get { return _supportedTypes; }
        }

        public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
        {
            if (Attribute.IsDefined(type, typeof(DataContractAttribute)))
            {
                try
                {
                    object instance = Activator.CreateInstance(type);

                    IEnumerable<MemberInfo> members = ((IEnumerable<MemberInfo>)type.GetFields())
                        .Concat(type.GetProperties().Where(property => property.CanWrite && property.GetIndexParameters().Length == 0))
                        .Where((member) => Attribute.IsDefined(member, typeof(DataMemberAttribute)));
                    foreach (MemberInfo member in members)
                    {
                        DataMemberAttribute attribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute));
                        object value;
                        if (dictionary.TryGetValue(attribute.Name, out value) == false)
                        {
                            if (attribute.IsRequired)
                            {
                                throw new SerializationException(String.Format("Required DataMember with name {0} not found", attribute.Name));
                            }
                            continue;
                        }
                        if (member.MemberType == MemberTypes.Field)
                        {
                            FieldInfo field = (FieldInfo)member;
                            object fieldValue;
                            if (ConvertEnumToString && field.FieldType.IsEnum)
                            {
                                fieldValue = Enum.Parse(field.FieldType, value.ToString());
                            }
                            else
                            {
                                fieldValue = serializer.ConvertToType(value, field.FieldType);
                            }
                            field.SetValue(instance, fieldValue);
                        }
                        else if (member.MemberType == MemberTypes.Property)
                        {
                            PropertyInfo property = (PropertyInfo)member;
                            object propertyValue;
                            if (ConvertEnumToString && property.PropertyType.IsEnum)
                            {
                                propertyValue = Enum.Parse(property.PropertyType, value.ToString());
                            }
                            else
                            {
                                propertyValue = serializer.ConvertToType(value, property.PropertyType);
                            }
                            property.SetValue(instance, propertyValue);
                        }
                    }
                    return instance;
                }
                catch (Exception)
                {
                    return null;
                }
            }
            return null;
        }

        public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
        {
            Dictionary<string, object> dictionary = new Dictionary<string, object>();
            if (obj != null && Attribute.IsDefined(obj.GetType(), typeof(DataContractAttribute)))
            {
                Type type = obj.GetType();
                IEnumerable<MemberInfo> members = ((IEnumerable<MemberInfo>)type.GetFields())
                    .Concat(type.GetProperties().Where(property => property.CanRead && property.GetIndexParameters().Length == 0))
                    .Where((member) => Attribute.IsDefined(member, typeof(DataMemberAttribute)));
                foreach (MemberInfo member in members)
                {
                    DataMemberAttribute attribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute));
                    object value;
                    if (member.MemberType == MemberTypes.Field)
                    {
                        FieldInfo field = (FieldInfo)member;
                        if (ConvertEnumToString && field.FieldType.IsEnum)
                        {
                            value = field.GetValue(obj).ToString();
                        }
                        else
                        {
                            value = field.GetValue(obj);
                        }
                    }
                    else if (member.MemberType == MemberTypes.Property)
                    {
                        PropertyInfo property = (PropertyInfo)member;
                        if (ConvertEnumToString && property.PropertyType.IsEnum)
                        {
                            value = property.GetValue(obj).ToString();
                        }
                        else
                        {
                            value = property.GetValue(obj);
                        }
                    }
                    else
                    {
                        continue;
                    }
                    if (dictionary.ContainsKey(attribute.Name))
                    {
                        throw new SerializationException(String.Format("More than one DataMember found with name {0}", attribute.Name));
                    }
                    dictionary[attribute.Name] = value;
                }
            }
            return dictionary;
        }
    }

Note: This DataContractJavaScriptConverter will only handle DataContract classes defined in the assembly where it is placed. If you want classes from separate assemblies, modify the _supportedTypes list accordingly in the static constructror.

This can be used as follows -

    JavaScriptSerializer serializer = new JavaScriptSerializer();
    serializer.RegisterConverters(new JavaScriptConverter[] { new DataContractJavaScriptConverter(true) });
    DataObject dataObject = serializer.Deserialize<DataObject>(JsonData);

The DataObject class would look 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; }
    }

Please note that this solution doesn't handle EmitDefaultValue and Order properties supported by DataMember attribute.

Shiftless answered 8/7, 2009 at 19:49 Comment(1)
ASP.NET uses JavascriptSerializer when converting client json (via ajax to a WebMethod) to a .NET object, so users like me working on a website who want to handle client data have no choice but to use Microsoft's version. For serialization, we can of course use Newtonsoft, but deserialization requires this. Source: referencesource.microsoft.com/#System.Web.Extensions/Script/…Unkind
D
0

Here is the generizied version of Tom Maher's answer. This will automatically register all nested property types in your DataObject to the converter so they can make use of the JsonPropertyAttribute.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Script.Serialization;
using System.Reflection;
using System.Collections;

public class JavascriptConverterFactory<T> : JavaScriptConverter where T : class
{
    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        List<MemberInfo> members = new List<MemberInfo>();
        members.AddRange(type.GetFields());
        members.AddRange(type.GetProperties().Where(p => p.CanRead && p.CanWrite && p.GetIndexParameters().Length == 0));

        object obj = Activator.CreateInstance(type);

        foreach (MemberInfo member in members)
        {
            JsonPropertyAttribute jsonProperty = (JsonPropertyAttribute)Attribute.GetCustomAttribute(member, typeof(JsonPropertyAttribute));
            if (jsonProperty != null && dictionary.ContainsKey(jsonProperty.Name))
            {
                SetMemberValue(serializer, member, obj, dictionary[jsonProperty.Name]);
            }
            else if (dictionary.ContainsKey(member.Name))
            {
                SetMemberValue(serializer, member, obj, dictionary[member.Name]);
            }
            else
            {
                KeyValuePair<string, object> kvp = dictionary.FirstOrDefault(x => string.Equals(x.Key, member.Name, StringComparison.InvariantCultureIgnoreCase));

                if (!kvp.Equals(default(KeyValuePair<string, object>)))
                {
                    SetMemberValue(serializer, member, obj, kvp.Value);
                }
            }
        }

        return obj;
    }

    private void SetMemberValue(JavaScriptSerializer serializer, MemberInfo member, object obj, object value)
    {
        if (member is PropertyInfo)
        {
            PropertyInfo property = (PropertyInfo)member;
            property.SetValue(obj, serializer.ConvertToType(value, property.PropertyType), null);
        }
        else if (member is FieldInfo)
        {
            FieldInfo field = (FieldInfo)member;
            field.SetValue(obj, serializer.ConvertToType(value, field.FieldType));
        }
    }


    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {
        Type type = obj.GetType();
        List<MemberInfo> members = new List<MemberInfo>();
        members.AddRange(type.GetFields());
        members.AddRange(type.GetProperties().Where(p => p.CanRead && p.CanWrite && p.GetIndexParameters().Length == 0));

        Dictionary<string, object> values = new Dictionary<string, object>();

        foreach (MemberInfo member in members)
        {
            JsonPropertyAttribute jsonProperty = (JsonPropertyAttribute)Attribute.GetCustomAttribute(member, typeof(JsonPropertyAttribute));

            if (jsonProperty != null)
            {
                values[jsonProperty.Name] = GetMemberValue(member, obj);
            }
            else
            {
                values[member.Name] = GetMemberValue(member, obj);
            }
        }

        return values;
    }

    private object GetMemberValue(MemberInfo member, object obj)
    {
        if (member is PropertyInfo)
        {
            PropertyInfo property = (PropertyInfo)member;
            return property.GetValue(obj, null);
        }
        else if (member is FieldInfo)
        {
            FieldInfo field = (FieldInfo)member;
            return field.GetValue(obj);
        }

        return null;
    }

    public override IEnumerable<Type> SupportedTypes
    {
        get
        {
            return new[] { typeof(T) }.Concat(GetAllNestedTypes(typeof(T)));
        }
    }

    public static JavaScriptSerializer Create()
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        serializer.RegisterConverters(new[] { new JavascriptConverterFactory<T>() });

        return serializer;
    }

    private static Type[] GetAllNestedTypes(Type type)
    {
        ArrayList types = new ArrayList();
        AddNestedMemberPropertyTypesRecursively(types, type);
        return (Type[])types.ToArray(typeof(Type));
    }

    private static ArrayList AddNestedMemberPropertyTypesRecursively(ArrayList types, Type type)
    {
        List<MemberInfo> members = new List<MemberInfo>();
        members.AddRange(type.GetFields());
        members.AddRange(type.GetProperties().Where(p => p.CanRead && p.CanWrite && p.GetIndexParameters().Length == 0));

        Dictionary<string, object> values = new Dictionary<string, object>();

        foreach (MemberInfo member in members)
        {
            if (member is PropertyInfo)
            {
                PropertyInfo property = (PropertyInfo)member;
                bool isAlreadyInArray = types.Contains(property.PropertyType);

                if (!isAlreadyInArray)
                {
                    types.Add(property.PropertyType);
                    if (property.PropertyType.IsArray)
                    {
                        Type arrayElementType = property.PropertyType.GetElementType();
                        isAlreadyInArray = types.Contains(arrayElementType);
                        if (!isAlreadyInArray)
                        {
                            types.Add(arrayElementType);
                            AddNestedMemberPropertyTypesRecursively(types, arrayElementType);
                        }
                    }
                    AddNestedMemberPropertyTypesRecursively(types, property.PropertyType);
                }
            }
        }

        return types;
    }
}
using System;

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class JsonPropertyAttribute : Attribute
{
    public JsonPropertyAttribute(string name)
    {
        Name = name;
    }

    public string Name
    {
        get;
        set;
    }
}
public class DataObject
{
    [JsonProperty("nested:dataobject")]
    NestedDataObject nestedObject { get; set; }
}

public class NestedDataObject
{
    [JsonProperty("some-other-string")]
    string someOtherString { get; set; }
}

How to use:

public DataObject GetData(responseContent)
{
    JavaScriptSerializer serializer = JavascriptConverterFactory<DataObject>.Create();
    DataObject deserializedData = serializer.Deserialize<DataObject>(responseContent);
    return deserializedData;
}
Detonation answered 8/7, 2009 at 19:49 Comment(0)
R
0

My requirements included:

  • must honor the dataContracts
  • must deserialize dates in the format received in service
  • must handle colelctions
  • must target 3.5
  • must NOT add an external dependency, especially not Newtonsoft (I'm creating a distributable package myself)
  • must not be deserialized by hand

My solution in the end was to use SimpleJson(https://github.com/facebook-csharp-sdk/simple-json).

Although you can install it via a nuget package, I included just that single SimpleJson.cs file (with the MIT license) in my project and referenced it.

I hope this helps someone.

Riding answered 8/7, 2009 at 19:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.