jsonb-api
jsonb-api copied to clipboard
include @JsonbGraph to (de)serialize subset of Json-B entity
Would be nice to have filter/query cabapilities to (de)serialize only a subset of the Json-B entity per config.
For example:
// (de)serialize only "id", tasks and their name
@JsonbGraph(name= "ItemWithTasks" value = {"id", "tasks", "tasks.name"})
public class Item {
private int id;
private String description;
private Set<Task> tasks;
}
and use it like:
var item = new Item(..);
var toJson = JsonbBuilder.create().withGraph("ItemWithTasks").toJson(item);
var fromJson = JsonbBuilder.create().withGraph("ItemWithTasks").fromJson(toJson)
The @JsonbGraph can provide capabilities like GraphQL or JPA FetchGraph.
Hi @nimo23, what does prevent you to do it today with a custom view or @JsonbTransient? Sounds linked to the model mapping we discuss in a few other issues and I think adding another graph feature would duplicate the base feature, no?
Hi.
@JsonbTransient is not an option, because it prevents json-b to process the property.
The only option would be a custom view: That means a custom class which must be created AND must be initialized with the values from its origin class. Why creating a new class and initialize all its needed property only for Json-B processing?
Now imagine this:
@JsonbGraph(name= "ItemWithTasks" value = {"id", "tasks", "tasks.name"})
@JsonbGraph(name= "ItemWithDescription" value = {"id", "name", "tasks", "tasks.desc"})
@JsonbGraph(name= "ItemWithTime" value = {"id", "name", "tasks", "tasks.name", "tasks.time.start"})
public class Item{..}
Actually to simulate a @JsonbGraph I need to create 3 classes (ItemWithTasks, ItemWithDescription, ItemWithTime), initialize it with the values from the Item class hierarchy and put that class into Json-B config as root class only to get what I want: a custom json view created by json-b.
All the information is already within Item and could be traversed accordingly by its @JsonbGraph. No need to repeat code, maintain redundant classes and create new instances. This would be far better. Dont you think so?
I think adding another graph feature would duplicate the base feature
No, actually having not such a graph feature would "duplicate" code (new classes, instances), maintanance and memory consumption..
No, you just need an interface which replaces 1-1 your graph definition, no need to create real DTO - this is up to your programming style.
jsonb.toJson(instance, MyInterface.class)
So with your solution I have to provide 3 different interface classes and must pollute the root class with this interfaces only to provide 3 different json-b views? In my opinion, a generic json query language provided by @JsonbGraph would be the best solution. Look for example, the JPA entity graph or graph-ql, where we can define a portion of the entity hierarchy to be processed. Json-B could provide such like this. With @JsonbGraph the client can request only a portion of an entity graph (for example, by JAX-RS or the like, this would be a good alternative to graph-ql..)
Yes, with your solution i have to provide 3 different classes, pollute the original class with 3 annotations and spec must define a query language since jsonpointer is not enough - for arrays typically. Jpa is a not a good example becaude the query language is used to browse relationships, not to filter an entity so there is no equivalence there. It is also the least portable part of the spec and a very weighty part in all impl so I pretty convinced it is not a graph notion jsonb needs. A filter api on the marshalling can be more relevant and way way lighter. QL are really the last resort solution, jpa can defend it from sql side but json cant - and graphql is likely not a defender since its language is almost a programming one and people start to abandon it cause it does not match front (js) and back expectations+coverage (see falcon for example).
Can you think about the filter solution for your case?
Can you think about the filter solution for your case?
Yes, filtering is what I mean.
The JPA Entity Graph can filter by @NamedEntityGraph
// not typesafe
@NamedEntityGraph(name = "FetchWithTasks", attributeNodes = {
@NamedAttributeNode("name"),
@NamedAttributeNode(value = "tasks", subgraph = "description") }, subgraphs = {
Could u provide a filter solution example suitable for Json-B?
Maybe something like this:
// (de)serialize only those properties
public JsonbFilter getItemWithTasks(Item item){
return JsonbFilter.builder(JsonbFilter.INCLUDE)
.addProp(item.getName())
.addProp(item.getDescription())
.addProp(item.getTask()) // Item Object
.addProp(task.getName(), Item.class) // property within Item Object
.addProp(task.getUser().getName(), User.class) // property within User Object
.build();
}
// (de)serialize all properties but not the following
public JsonbFilter getItemWithoutTasks(Item item){
return JsonbFilter.builder(JsonbFilter.EXCLUDE)
.noProp(item.getTask()) // Item Object
.noProp(task.getName(), Item.class) // property within Item Object
.noProp(task.getUser().getName(), User.class) // property within User Object
.build();
}
Sure,
It would be close to https://github.com/apache/johnzon/blob/17328867451e459bc48436b8a8253aa3f9cbb023/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/SerializeValueFilterTest.java#L34 but with a context giving the name, value and path probably
Json-B filter should provide:
- naming the filter (by method name?)
- exclude properties from parent class and its child classes
- include properties from parent class and its child classes
and maybe a annotation @JsonbFilter("filterName") which can be put into JAX-RS components:
@GET
@Produces(MediaType.APPLICATION_JSON)
// this will generate a Item with filtered properties
@JsonbFilter("ItemWithTasks")
public Item getItem(){..}
With this, the client can request a custom object graph. No more need to send whole object graphs.
No, it is a function, not an annotation so no need to make it complex. See it as a visitor pattern allowing to filter the object graph.
I've tested the interface way and it's not working (only tested with Yasson, not Johnzon).
public interface PersonPreview {
String getFirstName();
String getLastName();
}
public class Person implements PersonPreview {
public String getFirstName() {
return "john";
}
public String getLastName() {
return "doe";
}
public LocalDate getBirthDate() {
return LocalDate.now().minusYears(50);
}
public LocalDate getDeathDate() {
return LocalDate.now().plusYears(10);
}
}
Person person = new Person();
Jsonb jsonb = JsonbBuilder.create();
System.out.println(jsonb.toJson(person));
System.out.println(jsonb.toJson(person, PersonPreview.class));
I get:
{"birthDate":"1971-07-23","deathDate":"2031-07-23","firstName":"john","lastName":"doe"}
{"birthDate":"1971-07-23","deathDate":"2031-07-23","firstName":"john","lastName":"doe"}
@rmannibucau Am I doing something wrong? Is it how the spec is supposed to work or do you think it's a bug in Yasson?
@kalgon spec didn't define in 1.0/2.0 the usage of the second parameter so using it or not is the same.
Guess the current trick is to do (note the impl can be enhanced but the idea remains):
final var json = jsonb.toJson(Proxy.newProxyInstance(person.getClass().getClassLoader(), new Class<?>[PersonView.class], (p, m, a) -> Person.class.getMethod(m.getName()).invoke(person)); System.out.println(json);
side note: a delegating the impl, an adapter of the view or a serializer works too indeed (last one avoiding any additional allocation).
Using johnzon internal, I would personally do it using a custom AccessMode (kind of introspector for jaxb) which enables to change a model programmatically. View is just the static way to do it.
The kind of API this thread spoke about is close to https://github.com/eclipse-ee4j/jsonb-api/pull/279 but I have to admit after having tested it that I only see drawback to get rid of annotations as the unique model and to duplicate adaptes, (de)serializers etc. It makes the spec fat, too verbose, with multiple entry points for no real end user gain IMHO. I also tested in terms of perf the gain to not do the allocation and with G1 gain is really null so I think I prefer the simplicity to the complexity there since it will not enable much use cases, it will just make the code harder to understand for most dev.
You should get a subset of your original JSON using JSON-P and give it to JSON-B for deserialization. I don't see a point of adding this functionality to JSON-B.