Как я могу создать JsonPatchDocument из сравнения двух объектов C #?

Учитывая, что у меня есть два объекта C # одного типа, я хочу сравнить их для создания JsonPatchDocument.

У меня есть класс StyleDetail, определенный следующим образом:

public class StyleDetail
    {
        public string Id { get; set; }
        public string Code { get; set; }
        public string Name { get; set; }
        public decimal OriginalPrice { get; set; }
        public decimal Price { get; set; }
        public string Notes { get; set; }
        public string ImageUrl { get; set; }
        public bool Wishlist { get; set; }
        public List<string> Attributes { get; set; }
        public ColourList Colours { get; set; }
        public SizeList Sizes { get; set; }
        public ResultPage<Style> Related { get; set; }
        public ResultPage<Style> Similar { get; set; }
        public List<Promotion> Promotions { get; set; }
        public int StoreStock { get; set; }
        public StyleDetail()
        {
            Attributes = new List<string>();
            Colours = new ColourList();
            Sizes = new SizeList();
            Promotions = new List<Promotion>();
        }
    }

если у меня есть два объекта StyleDetail

StyleDetail styleNew = db.GetStyle(123);
StyleDetail styleOld = db.GetStyle(456);

Теперь я хочу создать JsonPatchDocument, чтобы я мог отправить различия в свой REST API ... Как это сделать ??

JsonPatchDocument patch = new JsonPatchDocument();
// Now I want to populate patch with the differences between styleNew and styleOld - how?

в javascript есть библиотека для этого https://www.npmjs.com/package/rfc6902

Вычислить разницу между двумя объектами:

rfc6902.createPatch ({первый: 'Крис'}, {первый: 'Крис', последний: 'коричневый'});

[ { op: 'add', path: '/last', value: 'Brown' } ]

но я ищу реализацию С #


person jmc    schedule 29.04.2017    source источник
comment
Я знаю, что это уже немного устарело ... но ты когда-нибудь догадывался, как это сделать? Я ищу то же самое!   -  person brazilianldsjaguar    schedule 25.04.2018
comment
Вы можете использовать отражение, чтобы перебирать свойства и сравнивать их. См. Этот вопрос для примера итерации свойств: stackoverflow.com/questions/1198886/   -  person Jack A.    schedule 25.04.2018


Ответы (3)


Давайте злоупотребим тем, что ваши классы сериализуемы в JSON! Вот первая попытка создателя патча, который не заботится о вашем фактическом объекте, а только о JSON-представлении этого объекта.

public static JsonPatchDocument CreatePatch(object originalObject, object modifiedObject)
{
    var original = JObject.FromObject(originalObject);
    var modified = JObject.FromObject(modifiedObject);

    var patch = new JsonPatchDocument();
    FillPatchForObject(original, modified, patch, "/");

    return patch;
}

static void FillPatchForObject(JObject orig, JObject mod, JsonPatchDocument patch, string path)
{
    var origNames = orig.Properties().Select(x => x.Name).ToArray();
    var modNames = mod.Properties().Select(x => x.Name).ToArray();

    // Names removed in modified
    foreach (var k in origNames.Except(modNames))
    {
        var prop = orig.Property(k);
        patch.Remove(path + prop.Name);
    }

    // Names added in modified
    foreach (var k in modNames.Except(origNames))
    {
        var prop = mod.Property(k);
        patch.Add(path + prop.Name, prop.Value);
    }

    // Present in both
    foreach (var k in origNames.Intersect(modNames))
    {
        var origProp = orig.Property(k);
        var modProp = mod.Property(k);

        if (origProp.Value.Type != modProp.Value.Type)
        {
            patch.Replace(path + modProp.Name, modProp.Value);
        }
        else if (!string.Equals(
                        origProp.Value.ToString(Newtonsoft.Json.Formatting.None),
                        modProp.Value.ToString(Newtonsoft.Json.Formatting.None)))
        {
            if (origProp.Value.Type == JTokenType.Object)
            {
                // Recurse into objects
                FillPatchForObject(origProp.Value as JObject, modProp.Value as JObject, patch, path + modProp.Name +"/");
            }
            else
            {
                // Replace values directly
                patch.Replace(path + modProp.Name, modProp.Value);
            }
        }       
    }
}

Использование:

var patch = CreatePatch(
    new { Unchanged = new[] { 1, 2, 3, 4, 5 }, Changed = "1", Removed = "1" },
    new { Unchanged = new[] { 1, 2, 3, 4, 5 }, Changed = "2", Added = new { x = "1" } });

// Result of JsonConvert.SerializeObject(patch)
[
  {
    "path": "/Removed",
    "op": "remove"
  },
  {
    "value": {
      "x": "1"
    },
    "path": "/Added",
    "op": "add"
  },
  {
    "value": "2",
    "path": "/Changed",
    "op": "replace"
  }
]
person gnud    schedule 24.04.2018
comment
Кстати, вы можете использовать тот же код для сравнения строк JSON, просто создайте JObject из строк, а затем вызовите FillPatchForObject - person gnud; 25.04.2018
comment
Спасибо! Большая помощь. Но это не сработает правильно, если мы изменим Цвета, например, в классе StyleDetail. - person Manuel Quelhas; 11.03.2019
comment
Правильно ли сериализуется StyleDetail в JSON, если вы сериализуете его напрямую? В противном случае мой метод не сработает. Можете ли вы дать определение StyleDetail и два примера объектов, которые дают ошибку? - person gnud; 13.03.2019
comment
Разница здесь в массивах. Я не пытаюсь правильно обрабатывать массивы, я просто заменяю весь массив, если есть разница. Было бы несложно сравнивать элемент за элементом, но представьте, что у вас есть массив из 700 элементов, и вы удаляете элемент 0. Обнаружение таких изменений сложно. Наивный способ сравнения массивов закончится заменой элементов 0-698 и удалением элемента 699. - person gnud; 13.03.2019
comment
он отлично работает, когда все свойства являются объектами. Но если свойство является массивом, вместо этого патч заменит весь массив, что является избыточным и в некоторых случаях влияет на производительность. - person Wei J. Zheng; 06.11.2020
comment
вот обновленная версия, которая поддерживает замену элемента массива вместо всего массива gist.github.com/yww325/b71563462cb5 - person yww325; 19.12.2020

Вы можете использовать мой DiffAnalyzer. Он основан на отражении, и вы можете настроить глубину анализа.

https://github.com/rcarubbi/Carubbi.DiffAnalyzer

var before = new User { Id = 1, Name="foo"};
var after= new User  { Id = 2, Name="bar"};
var analyzer = new DiffAnalyzer();
var results = analyzer.Compare(before, after);
person rcarubbi    schedule 11.10.2018
comment
Возвращает ли это патч json? - person Oylex; 16.07.2020
comment
нет, вам все равно нужно преобразовать результаты в JsonPatch. Однако у вас будут все различия. - person rcarubbi; 16.07.2020

Вы можете использовать это

Вы можете установить с помощью NuGet, см. SimpleHelpers.ObjectDiffPatch на NuGet.org

PM> Install-Package SimpleHelpers.ObjectDiffPatch

Использовать:

StyleDetail styleNew = new StyleDetail() { Id = "12", Code = "first" };
StyleDetail styleOld = new StyleDetail() { Id = "23", Code = "second" };
var diff = ObjectDiffPatch.GenerateDiff (styleOld , styleNew );

// original properties values
Console.WriteLine (diff.OldValues.ToString());

// updated properties values
Console.WriteLine (diff.NewValues.ToString());
person Serg csharp .Net Developer    schedule 26.04.2018
comment
Это не отвечает на вопрос. Вы вручную добавляете заменяемые элементы в код, а не создаете патч-документ путем сравнения двух объектов. - person jmc; 26.04.2018
comment
Извините, я исправил. - person Serg csharp .Net Developer; 26.04.2018
comment
Этот проект выполняет различие, но не создает JsonPatchDocument. - person jmc; 27.04.2018