I updated from version 5 to version 6.0.8 of Json.net. However, my code stopped working and it took me a while to understand that in previous version, it was able to handle the case where a generic type was instantiated with a list. But now, it doesn't. My question is, are there any settings 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;
}
}
Would work for a case where the class was instantiated 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 the latest Json.Net, it fails and it throws an exception (Accessed JObject values with invalid key value: 0. Object property name expected.). The only work around I found is
var resultList = result.Data.ToObject<List<int>>();
int firstValue = resultList[0];
Obviously, this kind of defeat the purpose of dynamic and I'd rather go back to the old behavior. Anything I can do to tell Json.net that a T can be a List<int> ? I see that it has the metadata to know about it since the $type and $values property are visible in Result view of the dynamic object.
Any help is appreciated, thanks.
To help isolate the issue and answer the comment question, I created a test app. It turns out that the TypeNameHandling.All option is what causes the argument exception. I added comment in 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)
}
}
}
I don't see a bug here. The problem is that you seem to be expecting to get your original
Result<T>
object back even though you are specifying that the deserialized object type should be aJObject
. That will never work becauseResult<T>
is not aJObject
, and aJObject
cannot hold custom objects likeResult<T>
. AJObject
can only hold a collection ofJProperty
objects, and those can only hold values derived fromJToken
. This is by design.When you call
JsonConvert.DeserializeObject(json, typeof(JObject))
, that forces the return object to be aJObject
, even if the receiving variable is declared asdynamic
. Similarly,JObject.Parse()
will always return aJObject
.If you want to get your original objects back, honoring the embedded type information in the JSON, you should be using the overload of
JsonConvert.DeserializeObject()
without a type, passing it the sameJsonSerializationSettings
that you used to serialize: