Why can't Json.Net 6 deserialize T as a generic list in a dynamic object? (works in version 5)
I have updated from version 5 to 6.0.8 of Json.net. However, my code stops working and it took me a while to realize that in the previous version it was able to handle the case where the generic type was created using a list. But now it is not so. My question is, are there any settings that I need to tweak to get the old behavior?
In the old library class
public class Result<T>
{
public T Data
{
get;
set;
}
public int ResultCode
{
get;
set;
}
}
Will work for the case where the class was created with something like
Result<List<int>> result;
So, in my code, I was doing something like
dynamic result = JsonConvert.DeserializeObject(result, typeof(JObject));
int firstValue = (int)result.Data[0];
However with latest Json.Net it fails and throws an exception (Accessing JObject values with invalid key value: 0. Object property name expected.). The only job I found is
var resultList = result.Data.ToObject<List<int>>();
int firstValue = resultList[0];
Obviously, this kind of hitting the target is dynamic, and I would rather go back to the old behavior. Anything I can do to tell Json.net that T can be List <int>? I can see the metadata is aware of this since the $ type and $ values property are visible in the Result view for the dynamic object.
Any help is appreciated, thanks.
To help isolate the issue and answer the commenting question, I created a test application. It turns out that the TypeNameHandling.All parameter is causing the argument to be thrown. I added a comment to the code to show good / bad serialization. And yes, I think we need the All option in our code.
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShowingBug
{
public class InterestingResult
{
public string Information { get; set; }
}
public class Result<T>
{
public T Data { get; set; }
public int ResultCode { get; set; }
}
class Program
{
static void Main(string[] args)
{
var veryInteresting = new List<InterestingResult>();
veryInteresting.Add(new InterestingResult() { Information = "Good" });
Result<List<InterestingResult>> operationResult = new Result<List<InterestingResult>>();
operationResult.Data = veryInteresting;
operationResult.ResultCode = 1;
JsonSerializerSettings serializerSettings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
};
var json = JsonConvert.SerializeObject(operationResult, serializerSettings);
dynamic result = JObject.Parse(json);
string information = (string)result.Data[0].Information;
Console.Out.WriteLine(information);
//The above works, however after some digging...
// I found that the option we use is not Auto, but All
serializerSettings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
};
json = JsonConvert.SerializeObject(operationResult, serializerSettings);
result = JObject.Parse(json);
// Now, you get an ArgumentException from Json.net 6.0.8 on the following line
information = (string)result.Data[0].Information;
// It worked in previous version of Json.Net
// I'm using .net 4.5.2 (if that makes a difference)
}
}
}
source to share
I don't see an error here. The problem is that you seem to be expecting to return your original object Result<T>
even if you specify that the type of the deserialized object should be JObject
. This will never work because it Result<T>
does not JObject
and JObject
cannot contain custom objects such as Result<T>
. A JObject
can only contain a set of objects JProperty
, and they can only contain values derived from JToken
. This is by design.
When you call JsonConvert.DeserializeObject(json, typeof(JObject))
it makes the returned object be JObject
even if the receiving variable is declared as dynamic
. Likewise, JObject.Parse()
will always return JObject
.
If you want to get your original objects back while respecting the built-in type information in JSON, you should use the typeless overload JsonConvert.DeserializeObject()
, passing it the same JsonSerializationSettings
one you used to serialize:
JsonSerializerSettings serializerSettings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
};
dynamic result = JsonConvert.DeserializeObject(json, serializerSettings);
source to share