Skip to content

Instantly share code, notes, and snippets.

@aoudiamoncef
Last active February 6, 2024 08:35
Show Gist options
  • Save aoudiamoncef/e2c63a69a6520866db2e12e69bb96a1b to your computer and use it in GitHub Desktop.
Save aoudiamoncef/e2c63a69a6520866db2e12e69bb96a1b to your computer and use it in GitHub Desktop.
JSON Patch and JSON Merge Patch to objects using Jackson

JSON Patch and Merge Patch Utility

This utility class provides functionality for applying JSON Patch and JSON Merge Patch to objects using the Jackson library.

Features

  • JSON Patch: Apply partial updates to JSON documents conforming to RFC 6902.
  • JSON Merge Patch: Update JSON documents by merging contents as per RFC 7396.

Usage

  1. Include the Jackson library in your project.
  2. Utilize the provided utility class for applying JSON Patch and Merge Patch to objects.

Example

import com.fasterxml.jackson.databind.ObjectMapper;
import com.maoudia.tutorial.kernel.JsonPatchUtils;

public class Main {
    public static void main(String[] args) {
        ObjectMapper objectMapper = new ObjectMapper();

        // Example using JSON Patch
        Object originalObject = /* Your original object */;
        String patchJson = /* Your JSON Patch as a String */;
        Object patchedObject = JsonPatchUtils.applyJsonPatch(objectMapper, originalObject, patchJson);

        // Example using JSON Merge Patch
        Object originalObjectMerge = /* Your original object */;
        String mergePatchJson = /* Your JSON Merge Patch as a String */;
        Object patchedObjectMerge = JsonPatchUtils.applyJsonMergePatch(objectMapper, originalObjectMerge, mergePatchJson);
    }
}

References

package com.maoudia;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gravity9.jsonpatch.JsonPatch;
import com.gravity9.jsonpatch.JsonPatchException;
import com.gravity9.jsonpatch.mergepatch.JsonMergePatch;
import jakarta.validation.constraints.NotNull;
import java.util.Objects;
/**
* Utility class for applying JSON Patch and JSON Merge Patch to objects using Jackson.
*/
public final class JsonPatchUtils {
private JsonPatchUtils() {
// Private constructor to prevent instantiation
}
/**
* Apply a JSON Patch to an object.
*
* @param objectMapper Jackson ObjectMapper instance for JSON processing.
* @param originalObject The original object to be patched.
* @param patchJson JSON representation of the patch.
* @param <T> The type of the object.
* @return The patched object.
* @throws RuntimeException if there is an error applying the JSON patch.
*/
@NotNull
public static <T> T applyJsonPatch(@NotNull ObjectMapper objectMapper,
@NotNull T originalObject,
@NotNull String patchJson) {
JavaType targetType = objectMapper.getTypeFactory().constructType(originalObject.getClass());
return applyJsonPatch(objectMapper, originalObject, patchJson, targetType);
}
/**
* Apply a JSON Patch to an object.
*
* @param objectMapper Jackson ObjectMapper instance for JSON processing.
* @param originalObject The original object to be patched.
* @param patchJson JSON representation of the patch.
* @param targetType The target type of the patched object.
* @param <T> The type of the object.
* @return The patched object.
* @throws RuntimeException if there is an error applying the JSON patch.
*/
@NotNull
public static <T> T applyJsonPatch(@NotNull ObjectMapper objectMapper,
@NotNull T originalObject,
@NotNull String patchJson,
@NotNull JavaType targetType) {
Objects.requireNonNull(objectMapper, "ObjectMapper must not be null");
Objects.requireNonNull(originalObject, "Original object must not be null");
Objects.requireNonNull(patchJson, "JSON patch must not be null");
Objects.requireNonNull(targetType, "Target type must not be null");
try {
JsonNode originalNode = objectMapper.convertValue(originalObject, JsonNode.class);
JsonPatch jsonPatch = objectMapper.readValue(patchJson, JsonPatch.class);
JsonNode patchedNode = jsonPatch.apply(originalNode);
return objectMapper.treeToValue(patchedNode, targetType);
} catch (JsonProcessingException | JsonPatchException e) {
throw new RuntimeException(e);
}
}
/**
* Apply a JSON Merge Patch to an object.
*
* @param objectMapper Jackson ObjectMapper instance for JSON processing.
* @param originalObject The original object to be patched.
* @param mergePatchJson JSON representation of the merge patch.
* @param <T> The type of the object.
* @return The patched object.
* @throws RuntimeException if there is an error applying the JSON merge patch.
*/
@NotNull
public static <T> T applyJsonMergePatch(@NotNull ObjectMapper objectMapper,
@NotNull T originalObject,
@NotNull String mergePatchJson) {
JavaType targetType = objectMapper.getTypeFactory().constructType(originalObject.getClass());
return applyJsonMergePatch(objectMapper, originalObject, mergePatchJson, targetType);
}
/**
* Apply a JSON Merge Patch to an object.
*
* @param objectMapper Jackson ObjectMapper instance for JSON processing.
* @param originalObject The original object to be patched.
* @param mergePatchJson JSON representation of the merge patch.
* @param targetType The target type of the patched object.
* @param <T> The type of the object.
* @return The patched object.
* @throws RuntimeException if there is an error applying the JSON merge patch.
*/
@NotNull
public static <T> T applyJsonMergePatch(@NotNull ObjectMapper objectMapper,
@NotNull T originalObject,
@NotNull String mergePatchJson,
@NotNull JavaType targetType) {
Objects.requireNonNull(objectMapper, "ObjectMapper must not be null");
Objects.requireNonNull(originalObject, "Original object must not be null");
Objects.requireNonNull(mergePatchJson, "JSON merge patch must not be null");
Objects.requireNonNull(targetType, "Target type must not be null");
try {
JsonNode originalNode = objectMapper.convertValue(originalObject, JsonNode.class);
JsonMergePatch jsonMergePatch = objectMapper.readValue(mergePatchJson, JsonMergePatch.class);
JsonNode patchedNode = jsonMergePatch.apply(originalNode);
return objectMapper.treeToValue(patchedNode, targetType);
} catch (JsonProcessingException | JsonPatchException e) {
throw new RuntimeException(e);
}
}
}
package com.maoudia;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.maoudia.tutorial.kernel.JsonPatchUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class JsonPatchUtilsTest {
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
void testApplyJsonPatchOnProduct() {
// Given
Product originalProduct = new Product("Laptop", 1000.0, "Electronics");
String patchJson = "[{ \"op\": \"replace\", \"path\": \"/price\", \"value\": 1200.0 }]";
// When
Product patchedProduct = JsonPatchUtils.applyJsonPatch(objectMapper, originalProduct, patchJson);
// Then
assertEquals("Laptop", patchedProduct.getName());
assertEquals(1200.0, patchedProduct.getPrice());
assertEquals("Electronics", patchedProduct.getCategory());
}
@Test
void testApplyJsonMergePatchOnProduct() {
// Given
Product originalProduct = new Product("Laptop", 1000.0, "Electronics");
String mergePatchJson = "{ \"price\": \"1200.0\" }";
// When
Product patchedProduct = JsonPatchUtils.applyJsonMergePatch(objectMapper, originalProduct, mergePatchJson);
// Then
assertEquals("Laptop", patchedProduct.getName());
assertEquals(1200.0, patchedProduct.getPrice());
assertEquals("Electronics", patchedProduct.getCategory());
}
@Test
void testApplyJsonPatchWithInvalidJson() {
// Given
Product originalProduct = new Product("Laptop", 1000.0, "Electronics");
String invalidPatchJson = "invalid json";
// When & Then
assertThrows(RuntimeException.class, () ->
JsonPatchUtils.applyJsonPatch(objectMapper, originalProduct, invalidPatchJson));
}
@Test
void testApplyJsonMergePatchWithInvalidJson() {
// Given
Product originalProduct = new Product("Laptop", 1000.0, "Electronics");
String invalidMergePatchJson = "invalid json";
// When & Then
assertThrows(RuntimeException.class, () ->
JsonPatchUtils.applyJsonMergePatch(objectMapper, originalProduct, invalidMergePatchJson));
}
@Test
void testApplyJsonPatchWithNullObject() {
// Given
String patchJson = "[{ \"op\": \"replace\", \"path\": \"/price\", \"value\": 1200.0 }]";
// When & Then
assertThrows(NullPointerException.class, () ->
JsonPatchUtils.applyJsonPatch(objectMapper, null, patchJson));
}
@Test
void testApplyJsonMergePatchWithNullObject() {
// Given
String mergePatchJson = "{ \"price\": 1200.0 }";
// When & Then
assertThrows(NullPointerException.class, () ->
JsonPatchUtils.applyJsonMergePatch(objectMapper, null, mergePatchJson));
}
@Test
void testApplyJsonPatchWithNullPatch() {
// Given
Product originalProduct = new Product("Laptop", 1000.0, "Electronics");
// When & Then
assertThrows(NullPointerException.class, () ->
JsonPatchUtils.applyJsonPatch(objectMapper, originalProduct, null));
}
@Test
void testApplyJsonMergePatchWithNullPatch() {
// Given
Product originalProduct = new Product("Laptop", 1000.0, "Electronics");
// When & Then
assertThrows(NullPointerException.class, () ->
JsonPatchUtils.applyJsonMergePatch(objectMapper, originalProduct, null));
}
private static class Product {
private final String name;
private final double price;
private final String category;
@JsonCreator
public Product(@JsonProperty("name") String name,
@JsonProperty("price") double price,
@JsonProperty("category") String category) {
this.name = name;
this.price = price;
this.category = category;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
public String getCategory() {
return category;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment