5

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?

Community
  • 1
  • 1
  • 2
    The first thing I'd do is get rid of XML from the equation. It looks like you should be able to provide a [mcve] without any XML at all. – Jon Skeet Aug 15 '16 at 20:12
  • @JonSkeet do you mean from my post or from my solution? (Right now for better or worse I have to use the XML as part of the solution itself). – EJoshuaS - Stand with Ukraine Aug 15 '16 at 20:28
  • 1
    Just from your post, to isolate the problem. Solving software issues is mostly a matter of removing irrelevant aspects until you can see just the one problematic bit. – Jon Skeet Aug 15 '16 at 20:30
  • I agree, the XML itself doesn't seem to be the problem here. The main reason I included it at all is that that's what I'm using to get the type information for deserialization, although when I step through the code I can see that it's resolving the type I'm looking for so I suspect that that might not the problem either. – EJoshuaS - Stand with Ukraine Aug 15 '16 at 20:35
  • 4
    Right. So provide a [mcve] which reproduces the problem as simply as possible. – Jon Skeet Aug 15 '16 at 20:46
  • I edited to remove the extra code. – EJoshuaS - Stand with Ukraine Aug 15 '16 at 20:51
  • But you haven't edited it to include a [mcve], which means we still can't reproduce it. – Jon Skeet Aug 16 '16 at 05:00
  • I'm confused - what am I missing here? I removed the XML like you suggested and I provided both the relevant code and a stack trace. – EJoshuaS - Stand with Ukraine Aug 16 '16 at 14:14
  • How is anyone meant to reproduce the problem with only what you've shown us? You haven't provided the JSON, for one thing... And ideally we should be able to copy, paste, compile and run... whereas you've provided snippets. – Jon Skeet Aug 16 '16 at 14:19
  • So your question boils down to "Why do none of the `Version` class constructors accept a version number argument that is `-1`"? – Good Night Nerd Pride Aug 16 '16 at 15:18
  • @Abbondanza I edited to clarify my question, in retrospect it was confusing what I was actually asking. This does actually seems like an odd behavior on my part given that something like new Version(1, 0, -1, -1) will actually result in a valid object state, but that's just my opinion. It does seem dumb to have to do Version(1, 0, 0, 0) just to make deserialization work given that build/revision number don't really have a meaning in the context of my application; my question is whether there's a way around that. – EJoshuaS - Stand with Ukraine Aug 16 '16 at 15:37

1 Answers1

5

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.

Good Night Nerd Pride
  • 8,245
  • 4
  • 49
  • 65