Search code examples
c#jsonjson.netjson-deserialization

Modify existing object with new partial JSON data using Json.NET


Consider the below example program

var calendar = new Calendar
{
    Id = 42,
    CoffeeProvider = "Espresso2000",
    Meetings = new[]
    {
        new Meeting
        {
            Location = "Room1",
            From = DateTimeOffset.Parse("2014-01-01T00:00:00Z"),
            To = DateTimeOffset.Parse("2014-01-01T01:00:00Z")
        },
        new Meeting
        {
            Location = "Room2",
            From = DateTimeOffset.Parse("2014-01-01T02:00:00Z"),
            To = DateTimeOffset.Parse("2014-01-01T03:00:00Z")
        },
    }
};

var patch = @"{
        'coffeeprovider': null,
        'meetings': [
            {
                'location': 'Room3',
                'from': '2014-01-01T04:00:00Z',
                'to': '2014-01-01T05:00:00Z'
            }
        ]
    }";

var patchedCalendar = Patch(calendar, patch);

The result of the Patch() method should be equal to calendar except as changed by patch. That means; Id would be unchanged, CoffeeProvider would be set to null and Meetings would contain a single item located in Room3.

  1. How does one create a general Patch() method that will work for any object (not just the example Calendar object) deserializable by Json.NET?

  2. If (1) this is not feasible, what would be some restrictions that would make it feasible and how would it be implemented?


Solution

  • You want JsonSerializer.Populate() or its static wrapper method JsonConvert.PopulateObject():

    Populates the JSON values onto the target object.

    For instance, here it is updating an instance of your Calendar class:

    public static class TestPopulate
    {
        public static void Test()
        {
            var calendar = new Calendar
            {
                Id = 42,
                CoffeeProvider = "Espresso2000",
                Meetings = new[]
                {
                    new Meeting
                    {
                        Location = "Room1",
                        From = DateTimeOffset.Parse("2014-01-01T00:00:00Z"),
                        To = DateTimeOffset.Parse("2014-01-01T01:00:00Z")
                    },
                    new Meeting
                    {
                        Location = "Room2",
                        From = DateTimeOffset.Parse("2014-01-01T02:00:00Z"),
                        To = DateTimeOffset.Parse("2014-01-01T03:00:00Z")
                    },
                }
            };
    
            var patch = @"{
        'coffeeprovider': null,
        'meetings': [
            {
                'location': 'Room3',
                'from': '2014-01-01T04:00:00Z',
                'to': '2014-01-01T05:00:00Z'
            }
        ]
    }";
            Patch(calendar, patch);
    
            Debug.WriteLine(JsonConvert.SerializeObject(calendar, Formatting.Indented));
        }
    
        public static void Patch<T>(T obj, string patch)
        {
            var serializer = new JsonSerializer();
            using (var reader = new StringReader(patch))
            {
                serializer.Populate(reader, obj);
            }
        }
    }
    

    And the debug output produced is:

    {
      "id": 42,
      "coffeeprovider": null,
      "meetings": [
        {
          "location": "Room3",
          "from": "2014-01-01T04:00:00+00:00",
          "to": "2014-01-01T05:00:00+00:00"
        }
      ]
    }
    

    Update

    If you want to copy first, you could do:

        public static T CopyPatch<T>(T obj, string patch)
        {
            var serializer = new JsonSerializer();
    
            var json = JsonConvert.SerializeObject(obj);
            var copy = JsonConvert.DeserializeObject<T>(json);
    
            using (var reader = new StringReader(patch))
            {
                serializer.Populate(reader, copy);
            }
    
            return copy;
        }