Search code examples
c#jsonreflectionjson.netjsonconverter

JsonConvert.DeserializeObject throws an exception when deserializing JSON with a hex value into an sbyte property


I have a machine-generated class MyData which has sbyte members. The actual class is long and not very readable, but here is a fragment of it:

class MyData
{
    private sbyte _MuxControl;
    public sbyte MuxControl 
    { 
        get { return _MuxControl; } 
        set { __isset.MuxControl = true; this._MuxControl = value; }
    }
}

The corresponding simplified JSON looks like this:

[ 
  { 
    "MuxControl": 0xAA
  }
]

I am attempting to deserialize like this:

var deserialized = JsonConvert.DeserializeObject<List<MyData>>(JsonStr);

Some values exceed sbyte range, for example 0xAA. As a result, exceptions are thrown. When I change the value to 0x1, for example, it works.

I can not touch the code of MyData. It's machine-generated. Is there a conversion setting, override or some other way to get these values to deserialize properly into sbyte?


Solution

  • You are getting the exception because the conversion methods Json.Net is using under the covers for sbyte are range checked, but what you really need here is an unchecked conversion (or more precisely, a bigger range). You can handle that with a custom JsonConverter like so:

    public class SByteConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(sbyte);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Integer)
            {
                // Integer values come in as longs from the reader.
                long val = (long)reader.Value;
    
                // If the value fits in 8 bits, convert it to a signed byte.
                if (val >= -128 && val <= 255)
                {
                    return unchecked((sbyte)val);
                }
    
                // We got a value that can't fit in an sbyte.
                throw new JsonSerializationException("Value was out of range for an sbyte: " + val);
            }
    
            // We got something we didn't expect, like a string or object.
            throw new JsonSerializationException("Unexpected token type: " + reader.TokenType);
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            // Write sbyte values out in the same format we read them.
            // Note this is technically invalid JSON per the spec.
            writer.WriteRawValue("0x" + ((sbyte)value).ToString("X2"));
        }
    }
    

    To use the converter, pass an instance of it to JsonConvert.DeserializeObject like this:

    var deserialized = JsonConvert.DeserializeObject<List<MyData>>(JsonStr, new SByteConverter());
    

    Working demo: https://dotnetfiddle.net/fEW6wy