What is JSON Merge Patch?
JSON Merge Patch is a format defined in RFC 7386, designed for use with the HTTP PATCH method to describe modifications to a target JSON document. It offers a simpler alternative to JSON Patch (RFC 6902) for updating JSON resources, especially when updates are object-based and don't require explicit null values or complicated array operations.
History and Use in API Development#
Standardized in October 2014, JSON Merge Patch emerged as a more straightforward method for partial resource updates compared to JSON Patch. While JSON Patch uses a list of operations (for example, add, remove, replace), JSON Merge Patch uses a patch document that mirrors the target document's structure. This makes it more human-readable and easier for simple updates. Here's an example of adding a new property to a JSON object
Original Document:
{
"name": "James Bond",
"age": 45
}
Patch Document:
{
"superpower": "Charm"
}
Resulting Document:
{
"name": "James Bond",
"age": 45,
"superpower": "Charm"
}
In API development, JSON Merge Patch is often preferred for straightforward updates that don't involve complicated operations like adding or removing array elements individually. It's particularly useful when only a subset of a resource's properties need updating, avoiding the need to send the entire resource or a list of operations.
How JSON Merge Patch Works#
JSON Merge Patch merges the patch document's contents with the target JSON document. Here's how it works:
- If the patch isn't a JSON object, it replaces the entire target document.
- If the patch is an object, it merges with the target object. For each member
in the patch:
- If the value is null, the corresponding member in the target is removed.
- If the member doesn't exist in the target, it's added.
- If the member exists, its value is replaced with the patch's value.
If you'd like to test out JSON Merge Patch for yourself, check out our new JSON Merge Patch tool and API.
Strengths and Weaknesses#
Strengths#
- Simplicity: Easier to read and write compared to JSON Patch.
- Human Readability: Intuitive for developers to understand what is changing at a glance.
Optimized for Updates#
JSON Merge Patch only transmits the changes needed, minimizing payload size. This makes it more efficient than sending entire JSON objects, reducing response times and bandwidth.
HTTP/1.1 PATCH
Content-Type: application/merge-patch+json
{
"id": "123456"
}
vs
HTTP/1.1 POST
Content-Type: application/json
{
"id": "123456",
"firstName": "John",
"lastName": "Doe",
"fullName": "John Doe",
"age": 21,
"country": "United States",
}
Notice how much smaller the first request is?
Weaknesses#
- Limited Array Operations: Can't add, remove, or change array elements individually; can only replace the entire array.
- No Recursive Behavior for Non-Objects: Doesn't support recursive operations for non-object patches.
- Null Values: Null values in the patch remove the corresponding field in the target document, and setting a field to null isn't possible if it doesn't exist in the target.
Examples of JSON Merge Patch Operations#
Adding a New Field#
Original Document:
{ "name": "James Bond", "age": 45 }
Patch Document:
{ "nickname": "007" }
Resulting Document:
{
"name": "James Bond",
"age": 45,
"nickname": "007"
}
Replacing a Field#
Original Document:
{
"name": "James Bond",
"age": 45,
"nickname": "007"
}
Patch Document:
{ "nickname": "008" }
Resulting Document:
{
"name": "James Bond",
"age": 45,
"nickname": "008"
}
Removing a field#
Original Document:
{
"name": "James Bond",
"age": 45,
"nickname": "007"
}
Patch Document:
{ "nickname": null }
Resulting Document:
{ "name": "James Bond", "age": 45 }
Replacing an Array#
Original Document:
{
"name": "James Bond",
"age": 45,
"movies": ["Dr. No", "From Russia With Love"]
}
Patch Document:
{ "movies": ["Goldfinger", "Thunderball"] }
Resulting Document:
{
"name": "James Bond",
"age": 45,
"movies": ["Goldfinger", "Thunderball"]
}
Tools and Libraries Using JSON Merge Patch#
Several tools and libraries implement the JSON Merge Patch standard:
- jsonmergepatch.com (Web and API): Free tool that lets you generate JSON Merge Patches from two JSON objects, or apply a JSON Merge Patch on a JSON object.
- json-merge-patch (NPM):
Provides functions to apply and generate JSON Merge Patches. Install with
npm install json-merge-patch
. - json-merge-patch
(Python): Implements
JSON Merge Patch for Python. Install with
pip install json-merge-patch
.
Generating JSON Merge Patch Changeset in Client-Side TypeScript#
If you are working on a client (ex. a React form), you will want to generate a
changeset client-side and send it to your API. Use the json-merge-patch
library to generate a JSON Merge Patch changeset in TypeScript:
import jsonMergePatch from "json-merge-patch";
// Original JSON object
const source = {
title: "Goodbye!",
author: {
givenName: "John",
familyName: "Doe",
},
};
// Target JSON object
const target = {
title: "Hello!",
author: {
familyName: null,
},
};
// Generate the patch
const patch = jsonMergePatch.generate(source, target);
console.log(patch);
// Output:
// {
// "title": "Hello!",
// "author": {
// "familyName": null
// }
// }
// Send the patch to the server
const headers = new Headers({
// This content-type is specified in the spec
// and is useful for the server to validate
"content-type": "application/merge-patch+json",
});
await fetch("https://your-backend.com/foo", { method: "PATCH", headers });
This code generates a patch that transforms the source
object into the
target
object.
Handling HTTP Patch Request in Node.js#
To handle an HTTP PATCH request in Node.js using JSON Merge Patch, parse the request body and apply the patch to the target JSON object. Here's an example using Express.js:
const express = require("express");
const jsonMergePatch = require("json-merge-patch");
const app = express();
app.use(express.json());
app.patch("/resource", (req, res) => {
// Assuming you have the original resource stored somewhere
const originalResource = {
title: "Goodbye!",
author: {
givenName: "John",
familyName: "Doe",
},
};
// Apply the patch to the original resource
const patchedResource = jsonMergePatch.apply(originalResource, req.body);
// Save the patched resource (e.g., to a database)
// For demonstration purposes, just log it
console.log(patchedResource);
res.status(200).send(patchedResource);
});
app.listen(3000, () => {
console.log("Server listening on port 3000");
});
In this example, the express.json()
middleware parses the JSON body of the
PATCH request, and the jsonMergePatch.apply
function applies the patch to the
original resource.
Validating a JSON Merge Patch Request Body#
To validate a JSON Merge Patch request body, ensure it conforms to the JSON Merge Patch format. Key points to check include:
- Content Type: Ensure the
Content-Type
header is set toapplication/merge-patch+json
. - JSON Object: The request body should be a valid JSON object.
- Patch Structure: The patch should only contain the changes, and any missing properties in the patch will be retained from the original object.
- Property-Level Security: Malicious callers might try and delete essential properties or modify data they are not supposed to using a JSON Merge Patch - making it critical to check which properties changed
Here's three ways of validating a JSON Merge Patch request body using Node.js:
Manual: Maintain a list of protected json-pointer
properties#
Using this approach, we maintain a list of JSON Pointers to properties we want to protect. Then, using lodash, we check if the properties have been modifed. Here's a complete code sample:
const _ = require("lodash");
const pointer = require("json-pointer");
const jsonMergePatch = require("json-merge-patch");
app.patch("/resource", (req, res) => {
if (req.headers["content-type"] !== "application/merge-patch+json") {
return res.status(415).send("Unsupported Media Type");
}
try {
const patch = req.body;
if (typeof patch !== "object" || Array.isArray(patch)) {
return res.status(400).send("Invalid JSON Merge Patch");
}
// Apply the patch (as shown in the previous example)
const originalResource = {
title: "Goodbye!",
author: {
givenName: "John",
familyName: "Doe",
},
};
const patchedResource = jsonMergePatch.apply(originalResource, patch);
// Validate protected properties are untouched
const protectedPropertyPointers = ["/author/familyName"];
for (const protectedPropertyPointer of protectedPropertyPointers) {
const originalValue = pointer.get(
originalResource,
protectedPropertyPointer,
);
const newValue = pointer.get(patchedResource, protectedPropertyPointer);
// Compare with lodash
if (!_.isEqual(oldValue, newValue)) {
res
.status(400)
.send(
`Invalid JSON Merge Patch. Property ${protectedPropertyPointer} cannot be modified`,
);
}
}
// Save the patched resource (e.g., to a database)
// For demonstration purposes, just log it
console.log(patchedResource);
res.status(204).send(patchedResource);
} catch (error) {
console.error(error);
res.status(400).send("Invalid JSON Merge Patch");
}
});
Faster: Use JSON Schema + AJV#
If your JSON object is schematized, you can also apply JSON Schema validation with a modified schema. The modifications needed are as follows:
- Remove all 'protected' properties to ensure they are not passed through the
request body. Also set
additionalProperties
tofalse
to avoid them being passed in anyways. - Remove
required
from objects, all properties are now optional since you don't include properties you aren't modifying in JSON Merge Patch - Allow
null
as a type on any property that can be deleted. Ex.{"type": "number}
would become{"type": ["number", "null"]}
Once your schema is ready, the AJV library can be used to apply it:
const Ajv = require("ajv");
const schema = require("your-modified-schema.json");
const ajv = new Ajv();
const validate = ajv.compile(schema);
const patch = { id: 1234 };
if (validate(patch)) {
console.log("Valid Patch");
} else {
console.log("Invalid Patch", validate.errors);
// Return a 400 error
}
Fastest + Simplest: Use Zuplo + OpenAPI#
Zuplo already supports JSON Schema validation through its Request Validation policy. Simply add the modified schema from the step above to your OpenAPI documentation for your PATCH endpoints and the policy will start validating requests. The policy will also automatically return an HTTP 400 error using the Problem Details format. Here's a step-by-step guide if you're unfamiliar with Zuplo. Side-benefit is that you now have a fully documented OpenAPI file, complete with schemas.
Building Efficient Application Programming Interfaces with JSON Merge Patch#
JSON Merge Patch is a powerful and efficient way to perform partial updates on JSON documents. By understanding how it works and how to implement it, developers can build more efficient APIs and applications that send less data over the network and process updates more quickly. If you're looking to implement JSON Merge Patch across your APIs or just want to modernize your APIs more generally, and want advice from an API expert - get in touch.