启用TypeNameHandling的JsonConvert序列化/反序列化锯齿数组

时间:2018-11-19 03:44:01

标签: c# json serialization json.net

我正在尝试反序列化具有锯齿状和多维数组属性的对象:

public abstract class Foo {}

public class Baz
{
    public readonly List<Foo> Foos;

    public Baz()
    {
        Foos = new List<Foo>();
    }
}

public class Bar : Foo
{
    public readonly double[][,,] Values;

    public Bar(double[][,,] values)
    {
        Values = values;
    }
}

由于Baz有一个List<Foo>并且Foo是一个抽象类,所以我想在序列化的字符串中保留Foo的类型,所以我必须使用TypeNameHandling.All

JsonSerializerSettings settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.All,
    Formatting = Formatting.Indented
};

但是,当我运行以下代码时:

var barValues = new double[][,,] { new double[,,] {{{ 1 }}} };

var baz = new Baz();
baz.Foos.Add(new Bar(barValues));

var json = JsonConvert.SerializeObject(baz, settings);
var baz2 = JsonConvert.DeserializeObject<Baz>(json, settings);

我有一个例外:

  

在JSON'System.Double [,] [],System.Private.CoreLib,   版本= 4.0.0.0,文化=中性,PublicKeyToken = 7cec85d7bea7798e'是   与'System.Double [,,] [],System.Private.CoreLib不兼容,   版本= 4.0.0.0,文化=中性,PublicKeyToken = 7cec85d7bea7798e'。   路径'Foos。$ values [0] .Values。$ type',第9行,位置63。'

如果我检查序列化的字符串,它看起来很奇怪:

"Values": {
   "$type": "System.Double[,][], System.Private.CoreLib",
   ...
}

在这种情况下,为什么JsonConvert无法反序列化字符串?

1 个答案:

答案 0 :(得分:2)

这似乎是TypeNameHandling.Arrays和等级大于2的多维数组的错误。

通过使用TypeNameHandling.Arrays序列化3d双精度数组,我可以更轻松地重现该问题:

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);

上面的代码生成的JSON是:

{
  "$type": "System.Double[,], mscorlib",
  "$values": [ [ [ 1.0 ] ] ]
}

可以预见到"$type"属性的存在,并记录在 TypeNameHandling setting 中,但是正如您注意到的那样,它看起来是错误的:它应该在数组类型如下:

  "$type": "System.Double[,,], mscorlib",

实际上,如果我像这样手动将[,]替换为[,,],就可以成功反序列化JSON:

// No exception!
JsonConvert.DeserializeAnonymousType(json.Replace("[,]", "[,,]"), root, settings)

最后,如果我尝试使用2d阵列而不是3d阵列进行相同的测试,则测试通过。演示小提琴here

原因在以下回溯调用中似乎是例程ReflectionUtils.RemoveAssemblyDetails中的错误:

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#

调用时,输入参数具有值

System.Double[,,], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

但是返回值是

System.Double[,], mscorlib

这显然是错误的。

如果需要,可以向Newtonsoft here报告问题。

更新:今天打开了一个类似的问题: Type of multi-dimensional array is incorrect #1918

一种解决方法,应该将输出类型信息的属性范围限制为给定JSON对象实际上可能是多态的情况。可能性包括:

  1. 您可以使用TypeNameHandling.None序列化对象图,但是用JsonPropertyAttribute.ItemTypeNameHandling = TypeNameHandling.Auto标记多态集合,如下所示:

    public class Baz
    {
        [JsonProperty(ItemTypeNameHandling = TypeNameHandling.Auto)]
        public readonly List<Foo> Foos;
    
        public Baz()
        {
            Foos = new List<Foo>();
        }
    }
    

    此解决方案可减少JSON的膨胀,并最大程度地减少了 TypeNameHandling caution in Newtonsoft Json External json vulnerable because of Json.Net TypeNameHandling auto?中描述的使用TypeNameHandling的安全风险,因此是首选方案。

  2. 您可以使用TypeNameHandling.None序列化对象图,并使用custom contract resolverJsonArrayContract.ItemTypeNameHandling设置为TypeNameHandling.Auto用于具有潜在多态项的集合,方法是覆盖{{ 3}}。

    如果无法将Json.NET属性添加到类型中,这将是解决方案。

  3. 您可以使用TypeNameHandling.AutoTypeNameHandling.Objects序列化对象图。

    这两个选项均可以避免该错误并减少JSON中的膨胀,但不会降低安全风险。

  4. 您可以使用DefaultContractResolver.CreateArrayContract序列化对象图。

    这避免了对RemoveAssemblyDetails()的调用,但导致JSON更加膨胀,并且无法避免可能的安全风险。