Search code examples
c#jsonjson.netdeserializationjson-deserialization

System.ArgumentOutOfRangeException while deserializing using Newtonsoft.Json


I am using Newtonsoft JSON to deserialize an object that contains interfaces. Newtonsoft was having trouble "figuring out" how to map the interfaces to their concrete types on deserialization, so I was following the directions in this answer to fix the issue.

I am doing the following to deserialize:

var converter = new JsonSerializer();
converter.Converters.Add(new DeviceCalibrationConverter());

// Obviously parameter.value and typeObj being the JSON and Type respectively
// I can see stepping through this that these are, in fact, the correct values
object deserialized = converter.Deserialize(new StringReader(parameter.Value), typeObj);

I'm using the DeviceCalibrationConverter object to try to map my interface to its concrete type:

public class DeviceCalibrationConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        // I am trying to map the IDeviceCalibration interface to its concrete type (DeviceCalibration)
        return objectType.Equals(typeof(IDeviceCalibration));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return serializer.Deserialize(reader, typeof(DeviceCalibration));
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

I'm currently getting an "ArgumentOutOfRangeException." Full exception details are below:

System.ArgumentOutOfRangeException was unhandled
  HResult=-2146233086
  Message=Version's parameters must be greater than or equal to zero.
Parameter name: build
  Source=mscorlib
  ParamName=build
  StackTrace:
       at System.Version..ctor(Int32 major, Int32 minor, Int32 build, Int32 revision)
       at Void .ctor(Int32, Int32, Int32, Int32)(Object[] )
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObjectUsingCreatorWithParameters(JsonReader reader, JsonObjectContract contract, JsonProperty containerProperty, ObjectConstructor`1 creator, String id)
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, String id, Boolean& createdFromNonDefaultCreator)
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
       at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
       at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
       at Newtonsoft.Json.JsonSerializer.Deserialize(TextReader reader, Type objectType)
       at FunctionalTesting.ExecuteXMLScript.Execute() in [folder]\ExecuteXMLScript.cs:line 141
       at FunctionalTesting.TestRunner.RunTests() in [folder]\TestRunner.cs:line 102
       at FunctionalTesting.Program.Main(String[] args) in [folder]\Program.cs:line 43
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: 

This occurs, by the way, on the line where I try to call Deserialize.

Edit: The entire JSON is pretty lengthy but it turns out that the offending line is as follows:

{"State":"needs-translation","OriginalString":"LP","StringID":[id],"StringValue":"LP"}}],
"MarketingFeatures":null,
 "CDIDriver" {"Name":"[Product Name]",
 "Version":{"Major":1,"Minor":0,"Build":-1,"Revision":-1,"MajorRevision":-1,"MinorRevision":-1}}

In particular, in "Version" deserializes to:

{"Major":1,"Minor":0,"Build":-1,"Revision":-1,"MajorRevision":-1,"MinorRevision":-1}

This deserializes to the System.Version class and this is invalid, thus producing the exception I listed above.

CDIDriver, by the way, creates the Version object as follows:

Version = new Version((int)major, (int)minor);

This is perfectly valid, and the document does, in fact, say that using this constructor as described will set the build and revision to -1 (as shown in the JSON). My question, then, is if this is a perfectly valid, document state of the object, why does it produce this exception when I try to deserialize it?

I am aware that trying to do something like new Version(1, 0, -1, -1) will produce an exception and that this is the documented behavior. (This seems like very odd behavior given that that would result in a valid object state, but that's just my opinion). Is there some way around having to do:

new Version(1, 0, 0, 0)

just for the sake of making the deserialization work?


Solution

  • The Version has not been serialized in the right way.

    If done correctly it should loke like this:

    {
        ...
        "Version": "1.0"
    }
    

    This can be achieved by using the VersionConverter like so:

    var json = JsonConvert.SerializeObject(new Version(1, 0), new VersionConverter());
    

    Deserialization also has to use this converter:

    var obj = JsonConvert.DeserializeObject<Version>(json, new VersionConverter());
    

    Working example: https://dotnetfiddle.net/eAqwip

    Note that you can also annotate the de/serialized class with a JsonConverterAttribute to achieve the same automatically:

    public class DeviceCalibration
    {
        ...
    
        [JsonConverter(typeof(VersionConverter))]
        public Version Version { get; set }
    }
    

    If you don't have access to the serializer code, I'm afraid you will have to fix the Json string "by hand" or write your own VersionConverter that can handle -1 values.