The short version is: How to patch the JSON object contained in a Postgres jsonb
field using Spring Data Rest PATCH method?
Here comes the long version, please consider the following entity:
@Entity
@Table(name = "examples")
public class Example {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String jsonobject;
@JsonRawValue
public String getJsonobject() {
return jsonobject == null ? null : jsonobject;
}
public void setJsonobject(JsonNode jsonobject) {
this.jsonobject = jsonobject == null ? null : jsonobject.toString();
}
}
jsonobject
is of Postgres type jsonb
. These getter/setter are the way to serialize/deserialize it for Spring Data Rest mentioned here. We also tried to give the field its own Type, as mentioned in these answers.
Our goal is to patch the JSON object this field contains, using Spring Data Rest.
For example:
GET /examples/1
{
"id": 1,
"jsonobject": {
"foo": {"bar": "Hello"},
"baz": 2
}
}
PATCH /examples/1
{
"jsonobject": {
"foo": {"bar": "Welcome"}
}
}
Expected output:
GET /examples/1
{
"id": 1,
"jsonobject": {
"foo": {"bar": "Welcome"},
"baz": 2
}
}
Current output:
GET /examples/1
{
"id": 1,
"jsonobject": {
"foo": {"bar": "Welcome"}
}
}
Spring Data Rest patches the Example resource and overrides the value for each requested attribute, instead of trying to dig into the JSON object's properties to only patch the requested nested properties.
This is when we thought Spring Data Rest support of application/merge-patch+json
and application/json-patch+json
media types would come in handy. Here are the outputs for each media type:
application/merge-patch+json
:
PATCH /examples/1
{
"jsonobject": {
"foo": {"bar": "Welcome"}
}
}
Output:
GET /examples/1
{
"id": 1,
"jsonobject": {
"foo": {"bar": "Welcome"}
}
}
application/json-patch+json
:
PATCH /examples/1
[
{ "op": "replace", "path": "/jsonobject/foo/bar", "value": "Welcome" }
]
Output:
{
"cause": {
"cause": null,
"message": "EL1008E:(pos 8): Property or field 'foo' cannot be found on object of type 'java.lang.String' - maybe not public?"
},
"message": "Could not read an object of type class com.example.Example from the request!; nested exception is org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 8): Property or field 'foo' cannot be found on object of type 'java.lang.String' - maybe not public?"
}
Which comes down to the same idea: only entity attributes are looked up, and either overridden entirely or not found.
The question is the following: is there a way to have Spring Data Rest understand it is dealing with a jsonb
field and therefore look for JSON nested properties rather than only looking up the entity attributes?
Nb: @Embeddable/@Embedded
annotations are most likely to be avoided, since they imply knowing the nested property names, which would lower the interest for a jsonb
field.
Thank you for reading.
jsonb_set
answers the problem but needs to be performed for each nested property. We wish the concatenate||
operator did not blindly overwrite missing properties, that would be way more useful. We'll accept your answer after a reply from David Siro. – Hysteria