Список десериализации Json.net дает повторяющиеся элементы

Я только что начал использовать Newtonsoft.Json(Json.net). В моем первом простом испытании я столкнулся с проблемой при десериализации общих списков. В моем примере кода ниже я сериализую объект, содержащий три типа простых целых списков (свойство, член var и массив).

Получившийся json выглядит отлично (списки преобразуются в json-массивы). Однако, когда я десериализую json обратно на новый объект того же типа, все элементы списка дублируются, ожидайте для массива. Я проиллюстрировал это, сериализуя его во второй раз.

От поиска, я прочитал, что может быть поле поддержки "private" для списков, которые также заполняет десериализатор.

Итак, мой вопрос: есть ли (желательно простой) способ избежать дублирования элементов в следующем случае?

Код

using System;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace JsonSerializeExample
{
    public class Program
    {
        static void Main()
        {
            var data = new SomeData();
            var json = JsonConvert.SerializeObject(data);
            Console.WriteLine("First : {0}", json);
            var data2 = JsonConvert.DeserializeObject<SomeData>(json);
            var json2 = JsonConvert.SerializeObject(data2);
            Console.WriteLine("Second: {0}", json2);
        }
    }

    public class SomeData
    {
        public string SimpleField;
        public int[] IntArray;
        public IList<int> IntListProperty { get; set; }
        public IList<int> IntListMember;

        public SomeData()
        {
            SimpleField = "Some data";
            IntArray = new[] { 7, 8, 9 };
            IntListProperty = new List<int> { 1, 2, 3 };
            IntListMember = new List<int> { 4, 5, 6 };
        }
    }
}

Результирующий выход

First : {"SimpleField":"Some data","IntArray":[7,8,9],"IntListMember":[4,5,6],"IntListProperty":[1,2,3]}
Second: {"SimpleField":"Some data","IntArray":[7,8,9],"IntListMember":[4,5,6,4,5,6],"IntListProperty":[1,2,3,1,2,3]}

Здесь может быть несколько совпадений с Json.Net дублирует личный список элементов. Тем не менее, я думаю, что моя проблема еще проще, и я до сих пор не понял этого.

Ответ 1

Это потому, что вы добавляете элементы в конструктор. Обычный подход в десериализаторах при обработке списка в основном:

  • прочитать список через getter
    • если список равен null: создайте новый список и назначьте его с помощью средства настройки свойств, если один
  • десериализуйте каждый элемент по очереди и добавьте (Add) в список

это происходит потому, что большинство членов списка не имеют сеттеров, т.е.

public List<Foo> Items {get {...}} // <=== no set

Контрастность к массивам, которая должна иметь полезность сеттера; поэтому подход обычно:

  • десериализуйте каждый элемент по очереди и добавьте (Add) во временный список
  • преобразовать список в массив (ToArray) и назначить через setter

Некоторые сериализаторы дают вам возможность контролировать это поведение (другие нет); и некоторые сериализаторы дают вам возможность полностью обойти конструктор (другие - нет).

Ответ 2

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

public class Appointment
{
    public List<AppointmentRevision> Revisions { get; set; }

    public AppointmentRevision CurrentRevision
    {
        get { return Revision.LastOrDefault(); }
    }

    public Appointment()
    {
        Revisions = new List<AppointmentRevision>();
    }
}

public class AppointmentRevision
{
    public List<Attendee> Attendees { get; set; }
}

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

public class Appointment
{
    public List<AppointmentRevision> Revisions { get; set; }

    [JsonIgnore]   
    public AppointmentRevision CurrentRevision
    {
        get { return Revision.LastOrDefault(); }
    }

    public Appointment()
    {
        Revisions = new List<AppointmentRevision>();
    }
}

Ответ 3

Как применить ObjectCreationHandling.Replace к выбранным свойствам при десериализации JSON?

Оказывается (я в 2019 году), вы можете установить элементы списка в своем конструкторе, как вы делали в своем вопросе. Я добавил атрибут ObjectCreationHandling.Replace над объявлением списка, после чего сериализация должна заменить все, что хранится в списке, на JSON.