重写 Json.Net 中的默认基元类型处理



有没有办法在处理基元类型时覆盖 Json.net 的默认反序列化行为?例如,将 json 数组反序列化时[3.14,10,"test"] object[] 类型为 3.14 的类型为 double,10 的类型为 long 。无论如何,我可以拦截或覆盖此类型决策,以便我可以分别将值反序列化为 decimalint

我基本上总是希望 json 整数始终以int返回,浮点数以decimal返回。这将节省我一些不得不注入double来在我的代码中decimal转换。

我已经研究了扩展Newtonsoft.Json.Serialization.DefaultContractResolver并实现我自己的Newtonsoft.Json.JsonConverter但我还没有发现任何实现这个所需覆盖的方法。

要重现的示例代码

object[] variousTypes = new object[] {3.14m, 10, "test"};
string jsonString = JsonConvert.SerializeObject(variousTypes);
object[] asObjectArray = JsonConvert.DeserializeObject<object[]>(jsonString); // Contains object {double}, object {long}, object {string}

不幸的是,JsonReader.SetToken(JsonToken, Object, Boolean)的方法不再是虚拟的。 在较新版本的 Json.NET(10.0.1 或更高版本(中,必须覆盖JsonReader.Read()并在那里进行必要的转换,然后使用所需的值类型更新读取器的值。

例如,如果您希望JsonTextReader尽可能返回Int32而不是Int64,则以下阅读器和扩展方法将完成这项工作:

public class PreferInt32JsonTextReader : JsonTextReader
{
    public PreferInt32JsonTextReader(TextReader reader) : base(reader) { }
    public override bool Read()
    {
        var ret = base.Read();
        // Read() is called for both an untyped read, and when reading a value typed as Int64
        // Thus if the value is larger than the maximum value of Int32 we can't just throw an 
        // exception, we have to do nothing.
        // 
        // Regardless of whether an Int32 or Int64 is returned, the serializer will always call
        // Convert.ChangeType() to convert to the final, desired primitive type (say, Uint16 or whatever).
        if (TokenType == JsonToken.Integer
            && ValueType == typeof(long)
            && Value is long)
        {
            var value = (long)Value;
            if (value <= int.MaxValue && value >= int.MinValue)
            {
                var newValue = checked((int)value); // checked just in case
                SetToken(TokenType, newValue, false);
            }
        }
        return ret;
    }
}
public static class JsonExtensions
{
    public static T PreferInt32DeserializeObject<T>(string jsonString, JsonSerializerSettings settings = null)
    {
        using (var sw = new StringReader(jsonString))
        using (var jsonReader = new PreferInt32JsonTextReader(sw))
        {
            return JsonSerializer.CreateDefault(settings).Deserialize<T>(jsonReader);
        }
    }
}

然后按如下方式使用它:

object[] variousTypes = new object[] { 3.14m, 10, "test" };
string jsonString = JsonConvert.SerializeObject(variousTypes);
var settings = new JsonSerializerSettings
{
    FloatParseHandling = FloatParseHandling.Decimal,
};
object[] asObjectArray = JsonExtensions.Int32PreferredDeserializeObject<object[]>(jsonString, settings);
// No assert
Assert.IsTrue(variousTypes.Select(o => o == null ? null : o.GetType()).SequenceEqual(asObjectArray.Select(o => o == null ? null : o.GetType())));

笔记:

  • JsonSerializerJsonReader都有一个设置FloatParseHandling,用于控制最初是将 JSON 中的浮点数解析为double还是decimal。 因此,没有理由在 Read() 中手动实现此转换。

  • 通过使用PreferInt32JsonTextReader可以控制序列化程序如何反序列化类型 object 的值。 以前整数 JSON 值将无条件反序列化为 long(如果需要,可以反序列化为BigInteger(。 现在,如果可能的话,int将被退回。 这也将修改 DataTableConverter 推断列类型的方式。

  • 尽管如此,使用 PreferInt32JsonTextReader 不会影响将 JSON 加载到具有 JToken.Load()JToken层次结构时发生的情况,因为构建层次结构的方法 JsonWriter.WriteToken() 会自动将所有整数值转换为 long

此处包含初步单元测试的示例源。

我认为这应该有效

public class MyReader : JsonTextReader
{
    public MyReader(string s) : base(new StringReader(s))
    {
    }
    protected override void SetToken(JsonToken newToken, object value)
    {
        object retObj = value;
        if (retObj is long) retObj = Convert.ChangeType(retObj, typeof(int));
        if (retObj is double) retObj = Convert.ChangeType(retObj, typeof(decimal));
        base.SetToken(newToken, retObj);
    }
}

object[] variousTypes = new object[] { 3.14m, 10, "test" };
string jsonString = JsonConvert.SerializeObject(variousTypes);
JsonSerializer serializer = new JsonSerializer();
var asObjectArray = serializer.Deserialize<object[]>(new MyReader(jsonString));

相关内容

  • 没有找到相关文章

最新更新