如何使用Spring Boot为嵌套的entites配置Jackson解串器

Jon*_*ira 8 java json jackson deserialization spring-boot

考虑以下实体:

package br.com.investors.domain.endereco;

import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.ComparisonChain;
import org.hibernate.validator.constraints.NotBlank;

import javax.persistence.*;
import java.io.Serializable;

import static com.google.common.base.Preconditions.checkArgument;
import static javax.persistence.GenerationType.SEQUENCE;

@Entity
public class Regiao implements Serializable, Comparable<Regiao> {

    @Id
    @GeneratedValue(strategy = SEQUENCE)
    private Long id;

    @Version
    private Long version;

    @NotBlank
    @Column(length = 100, unique = true)
    private String nome = "";

    Regiao() {}

    public Regiao(String nome) {
        checkArgument(!Strings.isNullOrEmpty(nome), "Nome não pode ser vazio");
        this.nome = nome;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Regiao) {
            Regiao o = (Regiao) obj;
            return Objects.equal(this.nome, o.nome);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(nome);
    }

    @Override
    public int compareTo(Regiao o) {
        return ComparisonChain.start()
                .compare(this.nome, o.nome)
                .result();
    }

    @Override
    public String toString() {
        return Objects.toStringHelper(getClass()).add("nome", nome).toString();
    }

    public Long getId() {
        return id;
    }

    public Long getVersion() {
        return version;
    }

    public String getNome() {
        return nome;
    }
}
Run Code Online (Sandbox Code Playgroud)

package br.com.investors.domain.endereco;

import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.ComparisonChain;
import org.hibernate.validator.constraints.NotBlank;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static javax.persistence.GenerationType.SEQUENCE;

@Entity
public class Cidade implements Serializable, Comparable<Cidade> {

    @Id
    @GeneratedValue(strategy = SEQUENCE)
    private Long id;

    @Version
    private Long version;

    @NotBlank
    @Column(length = 100, unique = true)
    private String nome = "";

    @NotNull
    @ManyToOne
    private Regiao regiao;

    @NotNull
    @ManyToOne
    private Estado estado;

    Cidade() {}

    public Cidade(String nome, Regiao regiao, Estado estado) {
        checkArgument(!Strings.isNullOrEmpty(nome), "Nome não pode ser vazio");
        checkNotNull(regiao, "Região não pode ser nulo");
        checkNotNull(estado, "Estado não pode ser nulo");

        this.nome = nome;
        this.regiao = regiao;
        this.estado = estado;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Cidade) {
            Cidade o = (Cidade) obj;
            return Objects.equal(this.nome, o.nome) &&
                    Objects.equal(this.estado, o.estado) &&
                    Objects.equal(this.regiao, o.regiao);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(nome, regiao, estado);
    }

    @Override
    public int compareTo(Cidade o) {
        return ComparisonChain.start()
                .compare(this.estado, o.estado)
                .compare(this.regiao, o.regiao)
                .compare(this.nome, o.nome)
                .result();
    }

    @Override
    public String toString() {
        return Objects.toStringHelper(getClass()).add("nome", nome).add("regiao", regiao).add("estado", estado).toString();
    }

    public Long getId() {
        return id;
    }

    public Long getVersion() {
        return version;
    }

    public String getNome() {
        return nome;
    }

    public Regiao getRegiao() {
        return regiao;
    }

    public Estado getEstado() {
        return estado;
    }
}
Run Code Online (Sandbox Code Playgroud)

我正在尝试将JSON发布到RestController

@RequestMapping(value = "/cidades", method = POST, consumes = APPLICATION_JSON_VALUE)
void inserir(@RequestBody Cidade cidade) {
    repository.save(cidade);
}
Run Code Online (Sandbox Code Playgroud)

我正在使用Spring Boot的默认配置来序列化和反序列化对象.

如果我发布这样的JSON,它可以正常工作:

{
    "nome": "Cidade",
    "regiao": "/10"
}
Run Code Online (Sandbox Code Playgroud)

但我需要发布一个这样的JSON:

{
    "nome": "Cidade",
    "regiao": {
        "id": 10,
        "version": 0,
        "nome": "regiao"
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我这样做,我会收到错误

{
    "timestamp": "2015-04-02",
    "status": 400,
    "error": "Bad Request",
    "exception": "org.springframework.http.converter.HttpMessageNotReadableException",
    "message": "Could not read JSON: Template must not be null or empty! (through reference chain: br.com.investors.domain.endereco.Cidade[\"regiao\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Template must not be null or empty! (through reference chain: br.com.investors.domain.endereco.Cidade[\"regiao\"])",
    "path": "/cidades/"
}
Run Code Online (Sandbox Code Playgroud)

做一些调试,我发现Jackson试图从发布对象的"regiao"属性创建一个URI,等待像"/ {id}"这样的字符串模板.我正在谷歌上搜索,但找不到合适的答案.

我在StackOverflow上看到了一些相关的问题,但没有一个对我有用.

你们能说出这个问题吗?

我认为这只是一种配置,但不知道如何或在哪里.

我也试图避免自定义序列化器和反序列化器.

编辑:

如果我只使用嵌套实体的id发布JSON,如下所示:

{
  "nome": "Cidade",
  "estado": "10",
  "regiao": "10"
}
Run Code Online (Sandbox Code Playgroud)

我收到这条消息:

{
    "timestamp": "2015-04-07",
    "status": 400,
    "error": "Bad Request",
    "exception": "org.springframework.http.converter.HttpMessageNotReadableException",
    "message": "Could not read JSON: Failed to convert from type java.net.URI to type br.com.investors.domain.endereco.Estado for value '10'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI 10. Is it local or remote? Only local URIs are resolvable. (through reference chain: br.com.investors.domain.endereco.Cidade[\"estado\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Failed to convert from type java.net.URI to type br.com.investors.domain.endereco.Estado for value '10'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI 10. Is it local or remote? Only local URIs are resolvable. (through reference chain: br.com.investors.domain.endereco.Cidade[\"estado\"])",
    "path": "/cidades"
}
Run Code Online (Sandbox Code Playgroud)

正如我所看到的那样,发送嵌套实体的正确方法就像"regiao":"/ 10",我在我的Javascript中对此进行了硬编码以解决此问题:

function(item) {
    item.regiao = "/" + item.regiao.id; //OMG
    item.estado = "/" + item.estado.id; //OMG!!

    if (item.id) {
        return $http.put('/cidades/' + item.id, item);
    } else {
        return $http.post('/cidades', item);
    }
}
Run Code Online (Sandbox Code Playgroud)

它工作但很糟糕.如何在Javascript或配置Jackson中修复此问题?

阅读一些文档,与UriToEntityConverter有关,但仍然不知道配置它的正确方法.

谢谢.

Jon*_*ira 4

我通过 EstadoRepository 和 RegiaoRepository 类上的 @RestResource(exported = false) 注释解决了这个问题。

当它自动配置端点和东西时,它会从 spring 中“隐藏”de repo...