JsonConvert сериализует / десериализует зубчатый массив с включенным TypeNameHandling - PullRequest
0 голосов
/ 19 ноября 2018

Я пытаюсь десериализовать объект с помощью зубчатого и многомерного свойства массива:

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 голосов
/ 20 ноября 2018

Похоже, что это ошибка с TypeNameHandling.Arrays и многомерными массивами ранга> 2.

Я могу легче воспроизвести проблему, сериализовав трехмерный двойной массив, используя 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);

JSON, сгенерированный с помощью приведенного выше кода:

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

Наличие свойства "$type" следует ожидать и задокументировано в Параметр TypeNameHandling , но, как вы заметили, он выглядит неправильно: у него должно быть дополнительное измерение в типе массива, например:

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

И на самом деле я могу успешно десериализовать JSON, еслиЯ вручную заменяю [,] на [,,] следующим образом:

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

Наконец, если я попробую тот же тест с 2d-массивом вместо 3d-массива, тест пройден.Демонстрационная скрипка здесь .

Причиной является ошибка в подпрограмме 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 здесь .

Обновление: Сегодня была открыта аналогичная проблема: Неверный тип многомерного массива # 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, которые описаны в Предупреждение TypeNameHandling в Newtonsoft Json и Внешний jsonуязвим из-за Json.Net TypeNameHandling auto? и поэтому является предпочтительным решением.

  2. Вы можете сериализовать свой граф объектов с помощью TypeNameHandling.None и использовать пользовательский обработчик контрактов для установки JsonArrayContract.ItemTypeNameHandling в TypeNameHandling.Auto для коллекций с потенциально полиморфными элементами путем переопределения DefaultContractResolver.CreateArrayContract.

    Это будетРешение использовать, если вы не можете добавить атрибуты Json.NET к вашим типам.

  3. Вы можете сериализовать свой граф объектов с помощью TypeNameHandling.Auto или TypeNameHandling.Objects.

    Любой из вариантовпозволит избежать ошибки, а также уменьшить блот в вашем JSON, но не уменьшит ваши риски безопасности.

  4. Вы можете сериализовать свой граф объектов с помощью JsonSerializerSettings.TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Full.

    Это позволяет избежатьвызов RemoveAssemblyDetails(), но приводит к еще более раздутому JSON и не позволяет избежать возможных угроз безопасности.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...