Why it’s not a good idea to turn entities into JSON directly
The error is in wanting to turn your JPA entities into JSON. I’ve been a part of a project that’s been months behind just because of this.
Transforming JPA entities into JSON is problematic because of:
Bond problems - it’s hard to know who the father is and who the son 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 JPA entity format is not always the same as the format desired in JSON. For example, if you have an entity Usuario
with a field senha
, will not want the field senha
appearing in JSON. If you have an entity Vendedor
and you wonder how many sales he made in the month, JSON would get huge and polluted with all the sales information when the only thing you want is to know how many sales are.
You cannot contextualize JSON so that you have 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 of these problems stem from the fact that JPA mapping serves to make the application’s object-relational mapping, while JSON-object mapping serves a completely different purpose. By placing the two in the same classes, you end up mapping the database tables to JSON, which is usually not what you want to do.
The solution
The solution is to create a group of classes in parallel to represent your JSON. In another project other than the one mentioned above, when using this approach we had virtually no problems with JSON and this part of the project was extremely quiet and simple to work with.
In the middle of the project, we have separated these classes that represent JSON into a separate library so as not to run the risk of mixing (occasionally by accident). In separating them, any attempt to mix them with the entities became a compilation error, as the package with the entities was dependent on the package of classess representing JSON, but the reverse was 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 above classes are just a bunch of raw data and they have an immutable structure. This is because their only purpose is to only be used to structure Jsons and nothing more. They should have the desired JSON structure with nothing more and nothing less, so don’t worry too much about reusing them. They can be modeled to the liking of your tool, be it Jackson, GSON or any other you are using, leaving these classes free of any restrictions or rules of JPA.
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());
}
}
Your example hurts some REST design guidelines, such as when you cite the
Vendedor
withVenda
. Although there is a relationship between the resources, if you want to visualize the sales of a seller, the most efficient way to work this would be through the way/vendedores/{id}/vendas
, not returning objects implicitly. This avoids various problems of both memory waste and data traffic on the network.– Weslley Tavares
@Weslleytavares No. When you use the way
vendedores/{id}/vendas
, a service will answer for that URL, search for the corresponding seller withVendedor v = em.find(id);
and then produce the list of objects to be serialized withList<VendaPorVendedorJSON> lista = v.listarJsonVendas();
. There is no increase in network traffic with this and the REST mapping remains unchanged.– Victor Stafusa
As for memory consumption, it depends on the case. But in this above approach, consumption tends to be lower due to the fact that by transforming the entity into a JSON, you’ll probably end up accessing several parts of the object graph brought by JPA, producing a swollen JSON that occupies more memory and tends to be heavier in traffic. It is true that this is not necessarily going to happen, but it is very easy to control this by mapping the JSON into separate objects, while it is very difficult to control this by mapping the JSON directly into the entities.
– Victor Stafusa
Thanks @Victorstafusa, had already read something about it, it resembles the right DTO standard?
– DiegoAugusto
@Diegoaugusto It resembles so much that it is exactly what we are talking about. : ) - These classes are nothing less than Dtos.
– Victor Stafusa
haha, I get it. thanks! The answer was very enlightening.
– DiegoAugusto