This appears to be a bug with TypeNameHandling.Arrays
and multidimensional arrays of rank > 2.
I can reproduce the problem more easily by serializing a 3d double array using TypeNameHandling.Arrays
:
var root = new double[,,] { { { 1 } } };
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Arrays };
var json = JsonConvert.SerializeObject(root, Formatting.Indented, settings);
// Try to deserialize to the same type as root
// but get an exception instead:
var root2 = JsonConvert.DeserializeAnonymousType(json, root, settings);
The JSON generated by the code above is:
{
"$type": "System.Double[,], mscorlib",
"$values": [ [ [ 1.0 ] ] ]
}
The presence of the "$type"
property is to be expected, and is documented in TypeNameHandling setting, but as you note it looks wrong: it should have an extra dimension in the array type like so:
"$type": "System.Double[,,], mscorlib",
And in fact I can deserialize the JSON successfully if I manually replace the [,]
with [,,]
like so:
// No exception!
JsonConvert.DeserializeAnonymousType(json.Replace("[,]", "[,,]"), root, settings)
Finally, if I try the same test with a 2d array instead of a 3d array, the test passes. Demo fiddle here.
The cause appears to be a bug in the routine ReflectionUtils.RemoveAssemblyDetails
when called at the following traceback:
Newtonsoft.Json.Utilities.ReflectionUtils.RemoveAssemblyDetails(string) C#
Newtonsoft.Json.Utilities.ReflectionUtils.GetTypeName(System.Type, Newtonsoft.Json.TypeNameAssemblyFormatHandling, Newtonsoft.Json.Serialization.ISerializationBinder) C#
Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.WriteTypeProperty(Newtonsoft.Json.JsonWriter, System.Type) C#
Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.WriteStartArray(Newtonsoft.Json.JsonWriter, object, Newtonsoft.Json.Serialization.JsonArrayContract, Newtonsoft.Json.Serialization.JsonProperty, Newtonsoft.Json.Serialization.JsonContainerContract, Newtonsoft.Json.Serialization.JsonProperty) C#
When called, the input parameter has the value
System.Double[,,], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
But the returned value is
System.Double[,], mscorlib
which is clearly wrong.
An issue could be reported to Newtonsoft here if desired.
Update: a similar issue was opened today: Type of multi-dimensional array is incorrect #1918.
As a workaround, you should limit the scope of properties for which you output type information to situations where a given JSON object might, in practice, be polymorphic. Possibilities include:
You could serialize your object graph with TypeNameHandling.None
but mark your polymorphic collections with JsonPropertyAttribute.ItemTypeNameHandling = TypeNameHandling.Auto
like so:
public class Baz
{
[JsonProperty(ItemTypeNameHandling = TypeNameHandling.Auto)]
public readonly List<Foo> Foos;
public Baz()
{
Foos = new List<Foo>();
}
}
This solution results in less bloated JSON and also minimizes the security risks of using TypeNameHandling
that are described in TypeNameHandling caution in Newtonsoft Json and External json vulnerable because of Json.Net TypeNameHandling auto? and thus is the preferable solution.
You could serialize your object graph with TypeNameHandling.None
and use a custom contract resolver to set JsonArrayContract.ItemTypeNameHandling
to TypeNameHandling.Auto
for collections with potentially polymorphic items, by overriding DefaultContractResolver.CreateArrayContract
.
This would be the solution to use if you cannot add Json.NET attributes to your types.
You could serialize your object graph with TypeNameHandling.Auto
or TypeNameHandling.Objects
.
Either option will avoid the bug and also reduce bloat in your JSON, but will not reduce your security risks.
You could serialize your object graph with JsonSerializerSettings.TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Full
.
This avoids the call to RemoveAssemblyDetails()
but results in even more bloated JSON and does not avoid the possible security risks.