Skip to main content

juneau-bean-jsonpatch

The juneau-bean-jsonpatch module provides Java beans for JSON Patch (RFC 6902), the canonical patch document format used by application/json-patch+json. A JSON Patch document is a JSON array of operation objects describing changes (add, remove, replace, move, copy, test) to apply to a target JSON document.

Overview​

This module models the JSON Patch wire format as typed @Marshalled POJOs with polymorphic dispatch on the spec's op discriminator. The beans round-trip through JsonSerializer / JsonParser once the parser is told to read the discriminator from the op member.

Key Features​

  • Abstract JsonPatchOperation base — Carries the shared path field (JSON Pointer per RFC 6901) and the @Marshalled(typePropertyName="op", dictionary={...}) discriminator wiring.
  • Six concrete subclasses — AddOp, RemoveOp, ReplaceOp, MoveOp, CopyOp, TestOp — each annotated with the matching @Marshalled(typeName=...) value.
  • Top-level array via extends LinkedList — JsonPatch extends LinkedList<JsonPatchOperation> so the document serialises as a top-level JSON array per the RFC (no @Json(wrapperAttr=...) wrapping). Mirrors the pattern used by JsonSchemaArray.
  • Open for extension — JsonPatchOperation is abstract, not sealed, so downstream callers can extend the dictionary with custom ops (vendor-specific test operators, JSON Patch extensions, etc.).
  • No external dependencies — Only depends on juneau-marshall.

Bean Classes​

ClassWire opExtra members
JsonPatchOperation— (abstract)path (JSON Pointer)
AddOp"add"value
RemoveOp"remove"—
ReplaceOp"replace"value
MoveOp"move"from (JSON Pointer)
CopyOp"copy"from (JSON Pointer)
TestOp"test"value
JsonPatch—LinkedList<JsonPatchOperation> — the document itself

Serializer / parser configuration​

The dictionary wiring tells the parser how to map op ⇒ subclass, but the serializer still needs to know to emit the op discriminator. JsonPatchMarshaller ships a pre-wired DEFAULT serializer/parser pair with that discriminator already configured, so callers don't need to hand-wire typePropertyName(JsonPatchOperation.class,"op") themselves:

import org.apache.juneau.bean.jsonpatch.*;

// Pre-configured serializer/parser, wired for the "op" discriminator.
JsonSerializer ser = JsonPatchMarshaller.SERIALIZER;
JsonParser par = JsonPatchMarshaller.PARSER;

If you need custom settings, build your own serializer/parser and still apply the same typePropertyName wiring:

import org.apache.juneau.marshall.json.*;
import org.apache.juneau.bean.jsonpatch.*;

JsonSerializer ser = JsonSerializer.create()
.addBeanTypes()
.typePropertyName(JsonPatchOperation.class, "op")
.build();

JsonParser par = JsonParser.create()
.typePropertyName(JsonPatchOperation.class, "op")
.build();

Basic Usage​

Building a patch document​

import org.apache.juneau.bean.jsonpatch.*;

JsonPatch patch = new JsonPatch()
.append(new TestOp("/a/b/c", "foo"))
.append(new RemoveOp("/a/b/c"))
.append(new AddOp("/a/b/c", java.util.List.of("foo", "bar")))
.append(new ReplaceOp("/a/b/c", 42))
.append(new MoveOp("/a/b/d", "/a/b/c"))
.append(new CopyOp("/a/b/e", "/a/b/d"));

// Static shortcuts on JsonPatchMarshaller use the pre-wired DEFAULT serializer/parser.
String wire = JsonPatchMarshaller.of(patch);
JsonPatch back = JsonPatchMarshaller.to(wire, JsonPatch.class);

The wire form is a top-level JSON array of operation objects, matching the example in RFC 6902 §3:

[
{"op":"test","path":"/a/b/c","value":"foo"},
{"op":"remove","path":"/a/b/c"},
{"op":"add","path":"/a/b/c","value":["foo","bar"]},
{"op":"replace","path":"/a/b/c","value":42},
{"op":"move","from":"/a/b/c","path":"/a/b/d"},
{"op":"copy","from":"/a/b/d","path":"/a/b/e"}
]

Polymorphic dispatch​

The parser materialises the concrete subclass keyed off the wire op value. Each entry in the parsed JsonPatch is an instance of the matching subclass — no manual instanceof / switch needed:

JsonPatch back = JsonPatchMarshaller.to(wire, JsonPatch.class);
for (JsonPatchOperation op : back) {
if (op instanceof AddOp add) { /* ... */ }
else if (op instanceof RemoveOp rem) { /* ... */ }
else if (op instanceof ReplaceOp rp) { /* ... */ }
// ...
}

Extending with a custom operation​

JsonPatchOperation is abstract, not sealed, so callers can add their own ops on top of the standard six. Subclass JsonPatchOperation, annotate with @Marshalled(typeName="your-op-name"), and register the subclass in a custom dictionary on your serializer / parser builder.

Content-Type​

The matching Content-Type constant lives in juneau-rest-common and has been available since 9.2.x:

import org.apache.juneau.http.header.ContentType;

ContentType.APPLICATION_JSON_PATCH; // "application/json-patch+json"

Resources​