使用 Spring Hateoas 的 jackson 和 Jackson2HalModule 反序列化 json 时为 Null id 属性
Null id property when deserialize json with jackson and Jackson2HalModule of Spring Hateoas
我的实体:
public class User {
private Integer id;
private String mail;
private boolean enabled;
// getters and setters
}
文件test.json(来自 REST 网络服务的响应):
{
"_embedded" : {
"users" : [ {
"id" : 1,
"mail" : "admin@admin.com",
"enabled" : true,
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/users/1"
}
}
} ]
}
}
还有我的测试 class :
public class TestJson {
private InputStream is;
private ObjectMapper mapper;
@Before
public void before() {
mapper = new ObjectMapper();
mapper.registerModule(new Jackson2HalModule());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
is = TestJson.class.getResourceAsStream("/test.json");
}
@After
public void after() throws IOException {
is.close();
}
@Test
public void test() throws IOException {
PagedResources<Resource<User>> paged = mapper.readValue(is, new TypeReference<PagedResources<Resource<User>>>() {});
Assert.assertNotNull(paged.getContent().iterator().next().getContent().getId());
}
@Test
public void testResource() throws IOException {
PagedResources<User> paged = mapper.readValue(is, new TypeReference<PagedResources<User>>() {});
Assert.assertNotNull(paged.getContent().iterator().next().getId());
}
}
第二次测试通过,但第一次没有通过。我不明白,因为用户中的id 属性 是唯一缺少的(邮件和启用的属性不为空)...
我该怎么做才能修复它?这是 Jackson 或 Spring Jackson2HalModule 中的错误吗?
您可以通过克隆我的 spring-hateoas 分支 repository 并启动单元测试来进行复制。
实际上,这是由于 Resource
class 构建来包装 bean 的内容。内容 属性 由 @JsonUnwrapped
注释,以便 Resource
class 可以将您的 bean 映射到此 属性 而在 json 中,bean 属性与_links
属性处于同一水平。使用此注解,可能会使 属性 名称与包装器和内部 bean 发生冲突。这里正是这种情况,因为 Resource
class 有一个 id
属性 继承自 ResourceSupport
class,而这个 属性可悲的是 @JsonIgnore
.
此问题有解决方法。您可以创建一个新的 MixIn
class 继承自 ResourceSupportMixin
class 并使用 @JsonIgnore(false)
注释覆盖 getId()
方法:
public abstract class IdResourceSupportMixin extends ResourceSupportMixin {
@Override
@JsonIgnore(false)
public abstract Link getId();
}
然后您只需将 IdResourceSupportMixin
class 添加到 ObjectMapper
:
mapper.addMixInAnnotations(ResourceSupport.class, IdResourceSupportMixin.class);
应该可以解决问题。
这对我有用:
public class User extends ResourceSupport {
@JsonIgnore(false)
private Integer id;
private String mail;
private boolean enabled;
// getters and setters
}
此外,将 http
客户端更改为 return PagedResources <User>
而不是 PagedResources<Resource<User>>
使用此代码,您可以找到所有 @Entity bean 并更改配置以公开 Id 值:
import java.util.LinkedList;
import java.util.List;
import javax.persistence.Entity;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
import org.springframework.stereotype.Component;
import com.rvillalba.exampleApiHateoas.entity.Example;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
public class SpringDataRestCustomization extends RepositoryRestConfigurerAdapter {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
listMatchingClasses(Entity.class).forEach(entity -> config.exposeIdsFor(entity));
}
public List<Class> listMatchingClasses(Class annotationClass) {
List<Class> classes = new LinkedList<Class>();
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(true);
scanner.addIncludeFilter(new AnnotationTypeFilter(annotationClass));
for (BeanDefinition bd : scanner.findCandidateComponents(Example.class.getPackage().getName())) {
try {
classes.add(Class.forName(bd.getBeanClassName()));
} catch (ClassNotFoundException e) {
log.error("listMatchingClasses problem", e);
}
}
return classes;
}
}
我的实体:
public class User {
private Integer id;
private String mail;
private boolean enabled;
// getters and setters
}
文件test.json(来自 REST 网络服务的响应):
{
"_embedded" : {
"users" : [ {
"id" : 1,
"mail" : "admin@admin.com",
"enabled" : true,
"_links" : {
"self" : {
"href" : "http://localhost:8080/api/users/1"
}
}
} ]
}
}
还有我的测试 class :
public class TestJson {
private InputStream is;
private ObjectMapper mapper;
@Before
public void before() {
mapper = new ObjectMapper();
mapper.registerModule(new Jackson2HalModule());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
is = TestJson.class.getResourceAsStream("/test.json");
}
@After
public void after() throws IOException {
is.close();
}
@Test
public void test() throws IOException {
PagedResources<Resource<User>> paged = mapper.readValue(is, new TypeReference<PagedResources<Resource<User>>>() {});
Assert.assertNotNull(paged.getContent().iterator().next().getContent().getId());
}
@Test
public void testResource() throws IOException {
PagedResources<User> paged = mapper.readValue(is, new TypeReference<PagedResources<User>>() {});
Assert.assertNotNull(paged.getContent().iterator().next().getId());
}
}
第二次测试通过,但第一次没有通过。我不明白,因为用户中的id 属性 是唯一缺少的(邮件和启用的属性不为空)...
我该怎么做才能修复它?这是 Jackson 或 Spring Jackson2HalModule 中的错误吗?
您可以通过克隆我的 spring-hateoas 分支 repository 并启动单元测试来进行复制。
实际上,这是由于 Resource
class 构建来包装 bean 的内容。内容 属性 由 @JsonUnwrapped
注释,以便 Resource
class 可以将您的 bean 映射到此 属性 而在 json 中,bean 属性与_links
属性处于同一水平。使用此注解,可能会使 属性 名称与包装器和内部 bean 发生冲突。这里正是这种情况,因为 Resource
class 有一个 id
属性 继承自 ResourceSupport
class,而这个 属性可悲的是 @JsonIgnore
.
此问题有解决方法。您可以创建一个新的 MixIn
class 继承自 ResourceSupportMixin
class 并使用 @JsonIgnore(false)
注释覆盖 getId()
方法:
public abstract class IdResourceSupportMixin extends ResourceSupportMixin {
@Override
@JsonIgnore(false)
public abstract Link getId();
}
然后您只需将 IdResourceSupportMixin
class 添加到 ObjectMapper
:
mapper.addMixInAnnotations(ResourceSupport.class, IdResourceSupportMixin.class);
应该可以解决问题。
这对我有用:
public class User extends ResourceSupport {
@JsonIgnore(false)
private Integer id;
private String mail;
private boolean enabled;
// getters and setters
}
此外,将 http
客户端更改为 return PagedResources <User>
而不是 PagedResources<Resource<User>>
使用此代码,您可以找到所有 @Entity bean 并更改配置以公开 Id 值:
import java.util.LinkedList;
import java.util.List;
import javax.persistence.Entity;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
import org.springframework.stereotype.Component;
import com.rvillalba.exampleApiHateoas.entity.Example;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
public class SpringDataRestCustomization extends RepositoryRestConfigurerAdapter {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
listMatchingClasses(Entity.class).forEach(entity -> config.exposeIdsFor(entity));
}
public List<Class> listMatchingClasses(Class annotationClass) {
List<Class> classes = new LinkedList<Class>();
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(true);
scanner.addIncludeFilter(new AnnotationTypeFilter(annotationClass));
for (BeanDefinition bd : scanner.findCandidateComponents(Example.class.getPackage().getName())) {
try {
classes.add(Class.forName(bd.getBeanClassName()));
} catch (ClassNotFoundException e) {
log.error("listMatchingClasses problem", e);
}
}
return classes;
}
}