Overriding Default Primitive Type Handling in Json.Net

后端 未结 2 674
盖世英雄少女心
盖世英雄少女心 2020-12-03 19:02

Is there a way to override the default deserialization behaviour of Json.net when handling primitive types? For example when deserializing the json array [3.14,10,\"te

相关标签:
2条回答
  • 2020-12-03 19:36

    Unfortunately the method JsonReader.SetToken(JsonToken, Object, Boolean) is no longer virtual. In more recent versions of Json.NET (10.0.1 or later), one must override JsonReader.Read() and do the necessary conversion there, then update the reader's value with the desired value type.

    For instance, if you would prefer your JsonTextReader to return Int32 instead of Int64 whenever possible, the following reader and extension method will do the job:

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

    Then use it as follows:

    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())));
    

    Notes:

    • Both JsonSerializer and JsonReader have a setting FloatParseHandling that controls whether floating point numbers from JSON are to be initially parsed as double or decimal. Thus there is no reason to implement this conversion manually in Read().

    • By using PreferInt32JsonTextReader you can control how the serializer deserializes values of type object. Previously integer JSON values would be unconditionally deserialized to long (or BigInteger if required). Now int will be returned if possible. This will also modify how column types are inferred by DataTableConverter.

    • Nevertheless, using PreferInt32JsonTextReader will not affect what happens when loading JSON into a JToken hierarchy with, say, JToken.Load(), because the method that builds the hierarchy, JsonWriter.WriteToken(), automatically converts all integer values to long.

    Sample source with preliminary unit tests here.

    0 讨论(0)
  • 2020-12-03 19:54

    I think, this should work

    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));
    
    0 讨论(0)
提交回复
热议问题