如何使用 Spring Boot 为嵌套实体配置 Jackson 反序列化器
How to configure Jackson deserializer for nested entites with 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;
}
}
和
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;
}
}
我正在尝试 POST 一个 JSON 到一个 RestController
@RequestMapping(value = "/cidades", method = POST, consumes = APPLICATION_JSON_VALUE)
void inserir(@RequestBody Cidade cidade) {
repository.save(cidade);
}
我正在使用 Spring 引导的默认配置来序列化和反序列化对象。
如果我 post 像这样 JSON,它工作正常:
{
"nome": "Cidade",
"regiao": "/10"
}
但我需要 post 一个 JSON 这样的:
{
"nome": "Cidade",
"regiao": {
"id": 10,
"version": 0,
"nome": "regiao"
}
}
如果我这样做,我会得到错误
{
"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/"
}
做一些调试,我发现 Jackson 试图从 posted 对象的 "regiao" 属性 创建一个 URI,等待像 "/{id 这样的字符串模板}”。我正在谷歌搜索,但找不到正确的答案。
我在 Whosebug 上看到了一些相关问题,但 none 对我有用。
大家能说说这是怎么回事吗?
我认为这只是一种配置,但不知道如何或在哪里。
我也在尝试避免自定义序列化器和反序列化器。
编辑:
如果我 POST 一个 JSON 只有嵌套实体的 id,像这样:
{
"nome": "Cidade",
"estado": "10",
"regiao": "10"
}
我收到这条消息:
{
"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"
}
正如我所见,发送嵌套实体的正确方式类似于 "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);
}
}
它有效,但很糟糕。
如何在 Javascript 或配置 Jackson 中解决此问题?
阅读了一些文档,与 UriToEntityConverter 有一些关系,但仍然不知道配置它的正确方法。
谢谢。
您可以像这样在您的实体上使用@JsonIgnoreProperties(ignoreUnknown = true) 注释class。
@Entity
@JsonIgnoreProperties(ignoreUnknown = true)
public class Area implements Serializable, CompanyAware, IdentifiableModel<Long> {
private static long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id = 0l;
@NotNull
@NotEmpty
@Column(nullable = false, unique = true)
private String name;
@NotNull
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(nullable = false)
private Region region;
private boolean active = true;
@ManyToOne
@JoinColumn(updatable = false)
private Company company;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public int hashCode() {
int hash = 0;
hash += (getId() != null ? getId().hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Area)) {
return false;
}
Area other = (Area) object;
if ((this.getId() == null && other.getId() != null) || (this.getId() != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
@Override
public String toString() {
return "Area[ id=" + getId() + " ]";
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the region
*/
public Region getRegion() {
return region;
}
/**
* @param region the region to set
*/
public void setRegion(Region region) {
this.region = region;
}
/**
* @return the active
*/
public boolean isActive() {
return active;
}
/**
* @param active the active to set
*/
public void setActive(boolean active) {
this.active = active;
}
/**
* @return the company
*/
public Company getCompany() {
return company;
}
/**
* @param company the company to set
*/
public void setCompany(Company company) {
this.company = company;
}
}
也许能解决您的问题。它将忽略 json 对象中未知的缺失字段。
它将仅使用 json 对象中的可用字段并忽略未知字段。
我用 EstadoRepository 和 RegiaoRepository 上的 @RestResource(exported = false) 注释解决了它 类。
它 "hides" 来自 spring 的回购,当它自动配置端点和东西时...
考虑以下实体:
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;
}
}
和
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;
}
}
我正在尝试 POST 一个 JSON 到一个 RestController
@RequestMapping(value = "/cidades", method = POST, consumes = APPLICATION_JSON_VALUE)
void inserir(@RequestBody Cidade cidade) {
repository.save(cidade);
}
我正在使用 Spring 引导的默认配置来序列化和反序列化对象。
如果我 post 像这样 JSON,它工作正常:
{
"nome": "Cidade",
"regiao": "/10"
}
但我需要 post 一个 JSON 这样的:
{
"nome": "Cidade",
"regiao": {
"id": 10,
"version": 0,
"nome": "regiao"
}
}
如果我这样做,我会得到错误
{
"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/"
}
做一些调试,我发现 Jackson 试图从 posted 对象的 "regiao" 属性 创建一个 URI,等待像 "/{id 这样的字符串模板}”。我正在谷歌搜索,但找不到正确的答案。
我在 Whosebug 上看到了一些相关问题,但 none 对我有用。
大家能说说这是怎么回事吗?
我认为这只是一种配置,但不知道如何或在哪里。
我也在尝试避免自定义序列化器和反序列化器。
编辑:
如果我 POST 一个 JSON 只有嵌套实体的 id,像这样:
{
"nome": "Cidade",
"estado": "10",
"regiao": "10"
}
我收到这条消息:
{
"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"
}
正如我所见,发送嵌套实体的正确方式类似于 "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);
}
}
它有效,但很糟糕。 如何在 Javascript 或配置 Jackson 中解决此问题?
阅读了一些文档,与 UriToEntityConverter 有一些关系,但仍然不知道配置它的正确方法。
谢谢。
您可以像这样在您的实体上使用@JsonIgnoreProperties(ignoreUnknown = true) 注释class。
@Entity
@JsonIgnoreProperties(ignoreUnknown = true)
public class Area implements Serializable, CompanyAware, IdentifiableModel<Long> {
private static long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id = 0l;
@NotNull
@NotEmpty
@Column(nullable = false, unique = true)
private String name;
@NotNull
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(nullable = false)
private Region region;
private boolean active = true;
@ManyToOne
@JoinColumn(updatable = false)
private Company company;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public int hashCode() {
int hash = 0;
hash += (getId() != null ? getId().hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Area)) {
return false;
}
Area other = (Area) object;
if ((this.getId() == null && other.getId() != null) || (this.getId() != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
@Override
public String toString() {
return "Area[ id=" + getId() + " ]";
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the region
*/
public Region getRegion() {
return region;
}
/**
* @param region the region to set
*/
public void setRegion(Region region) {
this.region = region;
}
/**
* @return the active
*/
public boolean isActive() {
return active;
}
/**
* @param active the active to set
*/
public void setActive(boolean active) {
this.active = active;
}
/**
* @return the company
*/
public Company getCompany() {
return company;
}
/**
* @param company the company to set
*/
public void setCompany(Company company) {
this.company = company;
}
}
也许能解决您的问题。它将忽略 json 对象中未知的缺失字段。 它将仅使用 json 对象中的可用字段并忽略未知字段。
我用 EstadoRepository 和 RegiaoRepository 上的 @RestResource(exported = false) 注释解决了它 类。
它 "hides" 来自 spring 的回购,当它自动配置端点和东西时...