Question

I am using the Newtonsoft.Json assembly to de-serialize a Json string into a dynamic object (ExpandoObject). The problem I am having is the int value is always returned as an Int64 where I am expecting an Int32. The code can be seen below.

namespace Serialization
{
    using System;
    using System.Collections.Generic;
    using System.Dynamic;
    using System.Linq;

    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;

    public static class JsonSerializer
    {
        #region Public Methods

        public static string Serialize(dynamic obj)
        {
            return JsonConvert.SerializeObject(obj);
        }

        public static dynamic Deserialize(string s)
        {
            var obj = JsonConvert.DeserializeObject(s);
            return obj is string ? obj as string : Deserialize((JToken)obj);
        }

        #endregion

        #region Methods

        private static dynamic Deserialize(JToken token)
        {
            // FROM : http://blog.petegoo.com/archive/2009/10/27/using-json.net-to-eval-json-into-a-dynamic-variable-in.aspx
            // Ideally in the future Json.Net will support dynamic and this can be eliminated.
            if (token is JValue) return ((JValue)token).Value;
            if (token is JObject)
            {
                var expando = new ExpandoObject();
                (from childToken in token
                 where childToken is JProperty
                 select childToken as JProperty).ToList().
                    ForEach(property => ((IDictionary<string, object>)expando).Add(property.Name, Deserialize(property.Value)));
                return expando;
            }
            if (token is JArray)
            {
                var items = new List<object>();
                foreach (var arrayItem in ((JArray)token)) items.Add(Deserialize(arrayItem));
                return items;
            }
            throw new ArgumentException(string.Format("Unknown token type '{0}'", token.GetType()), "token");
        }

        #endregion
    }
}

Normally I wouldn't notice this but this particular int is being used in reflection for some type checking and it fails miserably. Any ideas why this is happening would be greatly appreciated.

Was it helpful?

Solution

Cross-linking to answer https://stackoverflow.com/a/9444519/1037948

From How do I change the default Type for Numeric deserialization?

Paraphrased:

  • The author intentionally chose that all int's come back as Int64 to avoid overflow errors, and it's easier to check (for Json.NET internals, not you)
  • You can get around this with a custom converter like the one posted in the linked answer.

Here's a really generic converter; not entirely sure about the CanConvert check, but the important part that worked for me was allowing typeof(object):

/// <summary>
/// To address issues with automatic Int64 deserialization -- see https://stackoverflow.com/a/9444519/1037948
/// </summary>
public class JsonInt32Converter : JsonConverter
{
    #region Overrides of JsonConverter

    /// <summary>
    /// Only want to deserialize
    /// </summary>
    public override bool CanWrite { get { return false; } }

    /// <summary>
    /// Placeholder for inheritance -- not called because <see cref="CanWrite"/> returns false
    /// </summary>
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // since CanWrite returns false, we don't need to implement this
        throw new NotImplementedException();
    }

    /// <summary>
    /// Reads the JSON representation of the object.
    /// </summary>
    /// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader"/> to read from.</param><param name="objectType">Type of the object.</param><param name="existingValue">The existing value of object being read.</param><param name="serializer">The calling serializer.</param>
    /// <returns>
    /// The object value.
    /// </returns>
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return (reader.TokenType == JsonToken.Integer)
            ? Convert.ToInt32(reader.Value)     // convert to Int32 instead of Int64
            : serializer.Deserialize(reader);   // default to regular deserialization
    }

    /// <summary>
    /// Determines whether this instance can convert the specified object type.
    /// </summary>
    /// <param name="objectType">Type of the object.</param>
    /// <returns>
    /// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
    /// </returns>
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Int32) ||
                objectType == typeof(Int64) ||
                // need this last one in case we "weren't given" the type
                // and this will be accounted for by `ReadJson` checking tokentype
                objectType == typeof(object)
            ;
    }

    #endregion
}

OTHER TIPS

I was having a somewhat similar problem, but went ahead and and answered your question - casting to Int32, if possible and then Int16, if possible. I included tests as well. For future readers, it'd make sense to do this for other value types as well, but I only implemented signed integers here.

namespace Serialization
{
    using System;
    using System.Collections.Generic;
    using System.Dynamic;
    using System.Linq;

    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;

    public static class JsonSerializer
    {
        #region Public Methods

        public static string Serialize(dynamic obj)
        {
            return JsonConvert.SerializeObject(obj);
        }

        public static dynamic Deserialize(string s)
        {
            var obj = JsonConvert.DeserializeObject(s);
            return obj is string ? obj as string : Deserialize((JToken)obj);
        }

        #endregion

        #region Methods

        private static dynamic Deserialize(JToken token)
        {
            // FROM : http://blog.petegoo.com/archive/2009/10/27/using-json.net-to-eval-json-into-a-dynamic-variable-in.aspx
            // Ideally in the future Json.Net will support dynamic and this can be eliminated.
            if (token is JValue)
            {
                var value = ((JValue)token).Value;
                if (value is Int64)
                {
                    var lValue = (Int64)value;
                    if (Int32.MinValue <= lValue && lValue <= 0 || 0 < lValue && lValue <= Int32.MaxValue)
                    {
                        var iValue = (Int32)lValue;
                        value = iValue;
                        // Take out this if you don't want to cast down to Int16.
                        if (Int16.MinValue <= iValue && iValue <= 0 || 0 < iValue && iValue <= Int16.MaxValue)
                        {
                            value = (Int16)iValue;
                        }
                    }
                }
                return value;
            }
            if (token is JObject)
            {
                var expando = new ExpandoObject();
                (from childToken in token
                 where childToken is JProperty
                 select childToken as JProperty).ToList().
                    ForEach(property => ((IDictionary<string, object>)expando).Add(property.Name, Deserialize(property.Value)));
                return expando;
            }
            if (token is JArray)
            {
                var items = new List<object>();
                foreach (var arrayItem in ((JArray)token)) items.Add(Deserialize(arrayItem));
                return items;
            }
            throw new ArgumentException(string.Format("Unknown token type '{0}'", token.GetType()), "token");
        }

        #endregion
    }
}

namespace Serialization.Tests
{
    public class JsonSerializerTests
    {
        [Test]
        public void ShouldDeserializeAsInt16([Values(0, Int16.MaxValue, Int16.MinValue)] Int16 x)
        {
            var json = string.Format("{{ x: {0} }}", x);
            var dynamic = JsonSerializer.Deserialize(json);

            Assert.That(dynamic.x.GetType(), Is.EqualTo(typeof(Int16)));
        }

        [Test]
        public void ShouldDeserializeAsInt32([Values(Int16.MaxValue + 1, Int16.MinValue - 1)] Int32 x)
        {
            var json = string.Format("{{ x: {0} }}", x);
            var dynamic = JsonSerializer.Deserialize(json);

            Assert.That(dynamic.x.GetType(), Is.EqualTo(typeof(Int32)));
        }

        [Test]
        public void ShouldDeserializeAsInt64([Values(Int32.MaxValue + 1L, Int32.MinValue - 1L)] Int64 x)
        {
            var json = string.Format("{{ x: {0} }}", x);
            var dynamic = JsonSerializer.Deserialize(json);

            Assert.That(dynamic.x.GetType(), Is.EqualTo(typeof(Int64)));
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top