Why is not it a good idea to turn entities into JSON directly
The error lies in wanting to transform your JPA entities into JSON. I already participated in a project that delayed months only because of this.
Transforming JPA entities into JSON is problematic on account of:
-
Problems with ties - it is difficult to know who the father is and who the child is. Sometimes in some cases you will want the children telling you who the parents are and sometimes you will want the parents telling you who the children are.
-
The format of the JPA entity is not always the same as the format you want in JSON. For example, if you have a Usuario
entity with a senha
field, you will not want the senha
field appearing in JSON. If you have a Vendedor
entity and you want to know how many sales it has made in the month, JSON would look huge and polluted with all the sales information when the only thing you want is to know how many sales it is. li>
-
You can not contextualize JSON, so that it has a set of certain information in one context and a different set of information in another context.
-
There are cases where the data you need to export or import does not correspond directly to any of your entities.
All these problems come from the fact that JPA mapping is used to map object-relational mapping of the application, whereas object-JSON mapping has a completely different purpose. Putting the two in the same classes, you end up doing a mapping of the database tables to JSON, which is not usually what you want to do.
The solution
The solution is to create a group of classes in parallel to represent your JSON. In a different project than the one I mentioned above, when using this approach we had almost no problem with JSON and that part of the project was extremely quiet and simple to work with.
In the middle of the project, we separated these classes representing JSON into a separate library so as not to run the risk of mixing (from time to time it happened by accident). When separating them, any attempt to mix them with the entities became a compilation error, since the package with the entities depended on the package of classes that represent JSON, but the inverse does not.
For example:
public final class ProdutoJSON {
private final String nome;
private final Long id;
private final String descricaoTipo;
private final Long idTipo;
public ProdutoJSON(String nome, Long id, String descricaoTipo, Long idTipo) {
this.nome = nome;
this.id = id;
this.descricaoTipo = descricaoTipo;
this.idTipo = idTipo;
}
// Acrescente os getters aqui.
}
public final class TipoProdutoJSON {
private final String descricao;
private final Long id;
private final List<ProdutoPorTipoJSON> produtosListados;
public TipoProdutoJSON(String descricao, Long id, List<ProdutoPorTipoJSON> produtosListados) {
this.descricao = descricao;
this.id = id;
this.produtosListados = produtosListados;
}
// Acrescente os getters aqui.
}
public final class ProdutoPorTipoJSON {
private final String nome;
private final Long id;
public ProdutoPorTipoJSON(String nome, Long id) {
this.nome = nome;
this.id = id;
}
// Acrescente os getters aqui.
}
Note that the classes above are just a bunch of raw data and they have an immutable structure. This is because the only purpose for them is to only be used to structure JSONs and nothing else. They should have the desired JSON structure with nothing more and nothing less, so do not worry too much about reusing them. They can be modeled to the liking of your tool, be it Jackson, GSON or whatever you are using, making these classes free of any JPA restrictions or rules.
Here's how you instantiate these classes from their entities:
public class TipoProduto {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String descricao;
@OneToMany(mappedBy = "tipoProduto", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Produto> produtos;
// ...
public TipoProdutoJSON criarJsonComProdutos() {
List<ProdutoPorTipoJSON> p = produtos
.stream()
.map(Produto::criarJsonSemTipoProduto)
.collect(Collectors.toList());
return new TipoProdutoJSON(descricao, id, p);
}
}
public class Produto {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String nome;
@ManyToOne
@JoinColumn(name = "tipo_produto_id", nullable = true)
private TipoProduto tipoProduto;
// ...
public ProdutoPorTipoJSON criarJsonSemTipoProduto() {
return new ProdutoPorTipoJSON(nome, id);
}
public ProdutoJSON criarJsonComTipoProduto() {
return new ProdutoJSON(
nome,
id,
tipoProduto == null ? null : tipoProduto.getDescricao(),
tipoProduto == null ? null : tipoProduto.getId());
}
}