使用 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;
     }

 }