C# Newtonsoft.Json.Linq.JValue总是返回Int64

5

我正在使用Newtonsoft.Json程序集将Json字符串反序列化为动态对象(ExpandoObject)。 我遇到的问题是int值始终作为Int64返回,而我希望获得Int32。 以下是代码。

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
    }
}

通常情况下我不会注意到这个,但是这个特定的int正在被反射用于某些类型检查中,并且它失败得非常惨。如果你有什么想法,请不吝赐教。


使用jsonfx进行修复!https://github.com/jsonfx/jsonfx/downloads - Tri Q Tran
1
我同意你的观点。应该有一个反序列化为Int32的选项,或者一种嵌入Int32类型的方式。这目前给我带来了很多烦恼。 - Kasey Speakman
@KaseySpeakman 你有找到这个问题的解决方案吗? - Tri Q Tran
不,由于时间不足,我最终使用了BinaryMessageFormatter。 - Kasey Speakman
2个回答

6

交叉链接以回答https://dev59.com/B2sy5IYBdhLWcg3w0RXT#9444519

来自如何更改数字反序列化的默认类型?

改述:

  • 作者有意选择将所有int作为Int64返回,以避免溢出错误,并且更容易检查(对于Json.NET内部而言,不是您)
  • 您可以通过像链接答案中发布的那样使用自定义转换器来解决此问题。

这是一个非常通用的转换器;我不太确定CanConvert检查,但对我有效的重要部分是允许typeof(object)

/// <summary>
/// To address issues with automatic Int64 deserialization -- see https://dev59.com/B2sy5IYBdhLWcg3w0RXT#9444519
/// </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
}

这也是一个很好的参考资料https://dev59.com/inTYa4cB1Zd3GeqPxKj9,特别是[这个答案](https://dev59.com/inTYa4cB1Zd3GeqPxKj9#17749727)。 - drzaus
更新:最近开始看到关于 WriteJsonNotImplementedException,不确定出了什么问题... - drzaus

0
我之前遇到了一个类似的问题,但还是回答了你的问题 - 如果可能的话,先转换为Int32,然后再转换为Int16。我还包括了测试。对于未来的读者来说,这也适用于其他值类型,但我只在这里实现了有符号整数。
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)));
        }
    }
}

这似乎非常接近我所需要的,只是它有点老了 - 现在JSON.Net可以直接转换为ExpandoObject,但我想让它将任何int转换为Int32。大多数“JsonInt32Converters”只转换数字数组。你能指导我去哪里找到如何做到这一点的方法吗? - Michael Blackburn
1
@MichaelBlackburn 这是你想要的吗?https://dotnetfiddle.net/B4slQs - Jesus is Lord
是的,那很相似。我最终只是让它转换为Int64。我有很多其他的逻辑和验证要执行,所以我最终在所有的int值上都放置了一个缓冲检查和调整大小。谢谢! - Michael Blackburn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接