使用Spring Hateoas的Jackson2HalModule反序列化JSON时出现空ID属性问题

9

我的实体:

public class User {

    private Integer id;
    private String mail;
    private boolean enabled;

    // getters and setters
}

文件 test.json(来自 REST web 服务的响应):

{
 "_embedded" : {
  "users" : [ {
    "id" : 1,
    "mail" : "admin@admin.com",
    "enabled" : true,
    "_links" : {
      "self" : {
        "href" : "http://localhost:8080/api/users/1"
      }
    }
  } ]
 }
}

我的测试类:

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并启动单元测试来复现此问题。
3个回答

13

实际上,这是由Resource类引起的,它被构建为包装您的bean的内容。内容属性由@JsonUnwrapped注释,以便Resource类可以将您的bean映射到该属性,而在json中,bean属性位于与_links属性相同的级别上。使用此注释,可能会在包装器和内部bean之间出现属性名称冲突。正是因为Resource类具有从ResourceSupport类继承的id属性,并且该属性被遗憾地注释为@JsonIgnore

对于这个问题有一个解决方法。您可以创建一个新的MixIn类,继承自ResourceSupportMixin类,并用@JsonIgnore(false)注释覆盖getId()方法:

public abstract class IdResourceSupportMixin extends ResourceSupportMixin {

    @Override
    @JsonIgnore(false)
    public abstract Link getId();
}

然后,您只需将IdResourceSupportMixin类添加到您的ObjectMapper中:

mapper.addMixInAnnotations(ResourceSupport.class, IdResourceSupportMixin.class);

它应该解决这个问题。


1
spring-hateoasspring-boot一样的问题,所以我不得不将id重命名为其他名称。 - cahen
大约一年前,但如果我没记错的话,它实际上由于编译错误而无法工作... 我找到的唯一解决方案是cahen的解决方案。您必须将id属性重命名为其他名称。 - mfalaize
这对我有效:objectMapper.addMixIn(Resource.class, IdResourceSupportMixIn.class); - 适用于jackson 2.8.8和spring-hateoas 0.28以及rest template。但它会破坏response.getBody().getId() - František Hartman
1
ResourceSupportMixin是包私有的。我们无法扩展它。但我已经找到了解决方案。使用这个。public abstract class IdResourceSupportMixin extends ResourceSupport { @Override @JsonIgnore(false) public abstract Link getId(); @Override @XmlElement(name = "link") @JsonProperty("_links") @JsonInclude(JsonInclude.Include.NON_EMPTY) @JsonSerialize(using = Jackson2HalModule.HalLinkListSerializer.class) @JsonDeserialize(using = Jackson2HalModule.HalLinkListDeserializer.class) public abstract List getLinks(); } - hurelhuyag

1
使用此代码,您可以找到所有的@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;
     }

 }

这个解决方案对我起作用了。感谢 @Raul Villalba。我使用我的BaseEntity替换了Example类,其中我定义了private Long id,它可以正常工作。我还将类更改为public class SpringDataRestCustomization implements RepositoryRestConfigurer,因为RepositoryRestConfigurerAdapter已被弃用。https://docs.spring.io/spring-data/rest/docs/3.3.1.RELEASE/api/org/springframework/data/rest/webmvc/config/RepositoryRestConfigurerAdapter.html - biniam

0
这个对我有用:

public class User extends ResourceSupport {

    @JsonIgnore(false)
    private Integer id;
    private String mail;
    private boolean enabled;

    // getters and setters
}

此外,请将您的http客户端更改为返回PagedResources <User>,而不是PagedResources<Resource<User>>


如果ResourceSupport有public Link getId()方法,那么您如何为Integer id字段编写getter方法? - Mikhail Kopylov
@MikhailKopylov 您是正确的。@Will Franco - 这个解决方案不起作用。这是我看到的错误。'com.example.entity.User'中的'getId()'与'org.springframework.hateoas.ResourceSupport'中的'getId()'冲突;尝试使用不兼容的返回类型 - biniam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接