Подтвердить что ты не робот

Как заставить JSON.NET игнорировать отношения объектов?

Я работаю над проектом Entity Framework. Я хочу сериализовать кучу экземпляров класса сущностей. Я связал их вместе в класс контейнера:

public class Pseudocontext
{
    public List<Widget> widgets;
    public List<Thing> things;

Etcetera... это экземпляр этого класса, который я пытаюсь сериализовать. Я хочу, чтобы JSON.NET сериализовал члены каждого экземпляра класса сущности, которые фактически являются столбцами в базовой базе данных. Я не хочу, чтобы он даже пытался сериализовать ссылки на объекты.

В частности, мои классы объектов имеют виртуальные члены, которые позволяют мне писать код С#, который перемещает все мои отношения между сущностями, не беспокоясь о фактических значениях ключа, объединениях и т.д., и я хочу, чтобы JSON.NET игнорировал связанные части моих классов объектов.

На первый взгляд, похоже, есть опция конфигурации JSON.NET, которая делает именно то, о чем я говорю:

JsonSerializer serializer = new JsonSerializer();
serializer.PreserveReferencesHandling = PreserveReferencesHandling.None;

К сожалению, JSON.NET, похоже, игнорирует второе утверждение выше.

Я действительно нашел веб-страницу (http://json.codeplex.com/workitem/24608), где кто-то другой довел эту проблему до сведения самого Джеймса Ньютона-короля, и его ответ (в целом) был "Напишите пользовательский разрешитель контракта".

Как неадекватно, когда я нахожу этот ответ, я пытаюсь следовать его указаниям. Я очень хотел бы написать "контрактный резольвер", который игнорировал бы все, кроме примитивных типов, строк, объектов DateTime и моего собственного класса Pseudocontext, а также содержащихся в нем списков. Если у кого-то есть пример чего-то, что по крайней мере напоминает это, это может быть все, что мне нужно. Это то, что я придумал самостоятельно:

public class WhatDecadeIsItAgain : DefaultContractResolver
{
    protected override JsonContract CreateContract(Type objectType)
    {
        JsonContract contract = base.CreateContract(objectType);
        if (objectType.IsPrimitive || objectType == typeof(DateTime) || objectType == typeof(string)
            || objectType == typeof(Pseudocontext) || objectType.Name.Contains("List"))
        {
            contract.Converter = base.CreateContract(objectType).Converter;
        }
        else
        {
            contract.Converter = myDefaultConverter;
        }
        return contract;
    }
    private static GeeThisSureTakesALotOfClassesConverter myDefaultConverter = new GeeThisSureTakesALotOfClassesConverter();
}

public class GeeThisSureTakesALotOfClassesConverter : Newtonsoft.Json.Converters.CustomCreationConverter<object>
{
    public override object Create(Type objectType)
    {
        return null;
    }
}

Когда я пытаюсь использовать вышеизложенное (установив serializer.ContractResolver на экземпляр WhatDecadeIsItAgain до сериализации), я получаю ошибки OutOfMemory во время сериализации, которые указывают на то, что JSON.NET сталкивается с циклами ссылок, которые никогда не заканчиваются (несмотря на мой попытки сделать JSON.NET просто игнорировать ссылки на объекты).

Я чувствую, что мой "пользовательский разрешитель контракта" может быть неправильным. Как показано выше, он строится вокруг предпосылки, что я должен вернуть стандартный "контракт" для типов, которые я хочу сериализовать, и "контракт", который просто возвращает "нуль" для всех других типов.

Я понятия не имею, насколько правильны эти предположения, и это нелегко сказать. Дизайн JSON.NET в значительной степени основан на наследовании реализации, переопределении метода и т.д.; Я не очень похож на OOP, и я считаю, что такой дизайн будет довольно неясным. Если бы был реализован интерфейс "пользовательский контрактный резольвер", Visual Studio 2012 мог бы быстро и быстро завершить требуемые методы, а я представьте, что у меня будет небольшая проблема с заполнением заглушек реальной логикой.

У меня не возникло бы проблемы с написанием, например, метода, который возвращает "true", если я хочу сериализовать объект поставляемого типа и "false" в противном случае. Возможно, мне что-то не хватает, но я не нашел такого метода для переопределения и не смог найти гипотетический интерфейс (ICustomContractResolver?), Который бы сказал мне, что я на самом деле должен делать в последнем фрагменте кода вставленный выше.

Кроме того, я понимаю, что есть атрибуты JSON.NET([JsonIgnore]?), которые предназначены для решения подобных ситуаций. Я не могу использовать этот подход, потому что я использую "модель сначала". Если я не решит разорвать всю архитектуру проекта, мои классы объектов будут автоматически сгенерированы, и они не будут содержать атрибуты JsonIgnore, и я не чувствую себя комфортно, редактируя автоматизированные классы, чтобы содержать эти атрибуты.

Кстати, какое-то время у меня были вещи, созданные для сериализации ссылок на объекты, и я просто игнорировал все лишние данные "$ ref" и "$ id", которые JSON.NET возвращал в свой вывод сериализации. Я отказался от этого подхода на данный момент, по крайней мере, потому что (довольно внезапно) сериализация начала принимать чрезмерное количество времени (~ 45 минут, чтобы получить ~ 5 МБ JSON).

Я не смог связать это внезапное изменение в производительности до того, что я сделал. Во всяком случае, объем данных в моей базе данных ниже, чем тогда, когда сериализация фактически завершалась в разумные сроки. Но я был бы более чем счастлив вернуться к status quo ante (в котором мне просто нужно было игнорировать "$ ref", "$ id" и т.д.), Если бы это было возможно.

На этом этапе я также открыт для перспективы использования какой-либо другой библиотеки JSON или другой стратегии. Я чувствую, что могу просто использовать StringBuilder, System.Reflection и т.д., И придумал свое собственное домашнее решение... но не JSON.NET должен был легко справиться с этим видом?

4b9b3361

Ответ 1

Во-первых, для решения ваших проблем со ссылкой loops-- Параметр PreserveReferencesHandling определяет, будет ли Json.Net испускать $id и $ref для отслеживания $ref между объектами. Если для этого параметра установлено значение " None а граф объектов содержит циклы, то для предотвращения ошибок также необходимо установить для параметра " ReferenceLoopHandling цикл" значение " Ignore.

Теперь, чтобы заставить Json.Net полностью игнорировать все ссылки на объекты и сериализовать только примитивные свойства (за исключением, конечно, вашего класса Pseudocontext), вам, как вы и предлагали, нужен специальный Resolver Contract. Но не волнуйтесь, это не так сложно, как вы думаете. Средство распознавания имеет возможность внедрять метод ShouldSerialize для каждого свойства, чтобы контролировать, следует ли включать это свойство в выходные данные. Итак, все, что вам нужно сделать, это извлечь ваш преобразователь из значения по умолчанию, а затем переопределить метод CreateProperty, чтобы он соответствующим образом установил ShouldSerialize. (Здесь вам не нужен пользовательский JsonConverter, хотя эту проблему можно решить с помощью этого подхода. Однако для этого потребуется немного больше кода.)

Вот код для распознавателя:

class CustomResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty prop = base.CreateProperty(member, memberSerialization);

        if (prop.DeclaringType != typeof(PseudoContext) && 
            prop.PropertyType.IsClass && 
            prop.PropertyType != typeof(string))
        {
            prop.ShouldSerialize = obj => false;
        }

        return prop;
    }
}

Вот полная демонстрация, показывающая распознаватель в действии.

class Program
{
    static void Main(string[] args)
    {
        // Set up some dummy data complete with reference loops
        Thing t1 = new Thing { Id = 1, Name = "Flim" };
        Thing t2 = new Thing { Id = 2, Name = "Flam" };

        Widget w1 = new Widget
        {
            Id = 5,
            Name = "Hammer",
            IsActive = true,
            Price = 13.99M,
            Created = new DateTime(2013, 12, 29, 8, 16, 3),
            Color = Color.Red,
        };
        w1.RelatedThings = new List<Thing> { t2 };
        t2.RelatedWidgets = new List<Widget> { w1 };

        Widget w2 = new Widget
        {
            Id = 6,
            Name = "Drill",
            IsActive = true,
            Price = 45.89M,
            Created = new DateTime(2014, 1, 22, 2, 29, 35),
            Color = Color.Blue,
        };
        w2.RelatedThings = new List<Thing> { t1 };
        t1.RelatedWidgets = new List<Widget> { w2 };

        // Here is the container class we wish to serialize
        PseudoContext pc = new PseudoContext
        {
            Things = new List<Thing> { t1, t2 },
            Widgets = new List<Widget> { w1, w2 }
        };

        // Serializer settings
        JsonSerializerSettings settings = new JsonSerializerSettings();
        settings.ContractResolver = new CustomResolver();
        settings.PreserveReferencesHandling = PreserveReferencesHandling.None;
        settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
        settings.Formatting = Formatting.Indented;

        // Do the serialization and output to the console
        string json = JsonConvert.SerializeObject(pc, settings);
        Console.WriteLine(json);
    }

    class PseudoContext
    {
        public List<Thing> Things { get; set; }
        public List<Widget> Widgets { get; set; }
    }

    class Thing
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public List<Widget> RelatedWidgets { get; set; }
    }

    class Widget
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public bool IsActive { get; set; }
        public decimal Price { get; set; }
        public DateTime Created { get; set; }
        public Color Color { get; set; }
        public List<Thing> RelatedThings { get; set; }
    }

    enum Color { Red, White, Blue }
}

Выход:

{
  "Things": [
    {
      "Id": 1,
      "Name": "Flim"
    },
    {
      "Id": 2,
      "Name": "Flam"
    }
  ],
  "Widgets": [
    {
      "Id": 5,
      "Name": "Hammer",
      "IsActive": true,
      "Price": 13.99,
      "Created": "2013-12-29T08:16:03",
      "Color": 0
    },
    {
      "Id": 6,
      "Name": "Drill",
      "IsActive": true,
      "Price": 45.89,
      "Created": "2014-01-22T02:29:35",
      "Color": 2
    }
  ]
}

Надеюсь, что это находится в поле того, что вы искали.

Ответ 2

Кроме того, если вы ищете способ сделать это для всех классов моделей с разными типами имен типов (например, у вас есть некоторые модели, созданные Entity Framework) этот ответ может помочь, и вы можете игнорировать его свойства в сериализации JSON.

Ответ 3

Более простым способом является изменение шаблона модели T4 (.tt) для добавления [JsonIgnore] к вашим свойствам навигации, которые просто оставят примитивные типы как сериализуемые.