Search code examples
c#jsonasp.net-corejson-patch

JSON Patch update nested object


I'm been trying to use replace values within a nested object using JSON Patch, however I feel like I'm not getting the notation correct. Any ideas what the path should be?

I've created the following code to prove it in LINQPad 6.

void Main()
{
    var patchTest = new PatchTest();
    patchTest.Create();
    patchTest.ToString().Dump("Before Patch");
    var patch = JsonConvert.DeserializeObject<JsonPatchDocument<Contact>>(
        @"[
    {
      ""op"": ""replace"",
      ""path"": ""/firstname"",
      ""value"": ""Benjamin""
    },
    {
      ""op"": ""replace"",
      ""path"": ""age"",
      ""value"": ""29""
    },
    {
      ""op"": ""replace"",
      ""path"": ""//Appointment//Name"",
      ""value"": ""fsdfdsf""
    },
]");
    patchTest.Patch(patch);
    patchTest.ToString().Dump("After Patch");
}

public class PatchTest
{
    public Contact Contact { get; set; }

    public PatchTest() { }

    public void Create()
    {
        Contact = new Contact
        {
            FirstName = "Walt",
            LastName = "Banks",
            Age = 20
        };
    }

    public void Patch(JsonPatchDocument<Contact> patch)
    {
        patch.Replace(e => e.Appointment, Contact.Appointment);
        patch.ApplyTo(Contact);
    }

    public override string ToString()
    {
        return $"{nameof(Contact)}: {Contact}";
    }
}

public class Contact
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public Appointment Appointment { get; set; }

    public override string ToString()
    {
        return $"{nameof(FirstName)}: {FirstName}, {nameof(LastName)}: {LastName}, {nameof(Appointment)}: {Appointment}";
    }
}


public class Appointment
{
    public string Name { get; set; }

    public override string ToString()
    {
        return $"{nameof(Name)}: {Name}";
    }
}

However it fails to find Name

enter image description here


Solution

  • The reason it can't find the appointment name is because you've not initialised Appointment when creating your Contact. Change Create to:

    public void Create()
    {
        Contact = new Contact
        {
            FirstName = "Walt",
            LastName = "Banks",
            Age = 20,
            Appointment = new Appointment()
        };
    }
    

    Running your example in a console app now produces this output:

    Before Patch
    Contact: FirstName: Walt, LastName: Banks, Age: 20, Appointment: Name:
    After Patch
    Contact: FirstName: Benjamin, LastName: Banks, Age: 29, Appointment: Name: fsdfdsf
    

    I added Contact.Age to its ToString() override, as it was missing. Also, single / and double // both work in the path. I'm guessing you used the latter when trying to figure out what was wrong.

    Now, as you've already defined the document in JSON, you don't need to define another replacement operation. Your Patch method can be simplified to:

    public void Patch(JsonPatchDocument<Contact> patch)
    {
        patch.ApplyTo(Contact);
    }
    

    and the output will be the same as before. The equivalent of doing all of this in code, without manually creating the JSON document, would be as follows:

    public void Patch(Contact amendedContact)
    {
        var patch = new JsonPatchDocument<Contact>();
        patch.Replace(e => e.FirstName, amendedContact.FirstName);
        patch.Replace(e => e.Age, amendedContact.Age);
        patch.Replace(e => e.Appointment.Name, amendedContact.Appointment.Name);
        patch.ApplyTo(Contact);
    }
    

    and calling it like so:

    var amendedContact = new Contact
    {
        FirstName = "Benjamin",
        Age = 29,
        Appointment = new Appointment
        {
            Name = "fsdfdsf"
        }
    };
    
    patchTest.Patch(amendedContact);
    

    This again produces your desired output. But you'll still have to make sure nested properties are initialised, otherwise you'll run into the original problem.