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 aJObjectcannot hold custom objects likeResult<T>. AJObjectcan only hold a collection ofJPropertyobjects, 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 sameJsonSerializationSettingsthat you used to serialize: