Null id property when deserialize json with jackson and Jackson2HalModule of Spring Hateoas
Asked Answered
S

3

9

My entity :

public class User {

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

    // getters and setters
}

File test.json (response from REST webservice) :

{
 "_embedded" : {
  "users" : [ {
    "id" : 1,
    "mail" : "[email protected]",
    "enabled" : true,
    "_links" : {
      "self" : {
        "href" : "http://localhost:8080/api/users/1"
      }
    }
  } ]
 }
}

And my test 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());
    }
}

The second test passes but not the first. I don't understand because the id property in the user is the only one missing (mail and enabled properties are not empty)...

What do I have to do to fix it ? Is it a bug in Jackson or Spring Jackson2HalModule ?

You can reproduce by cloning my spring-hateoas fork repository and launching unit tests.

Spoliate answered 7/6, 2015 at 15:18 Comment(0)
S
13

Actually, it was due to the Resource class which is built to wrap the content of your bean. The content property is annotated by @JsonUnwrapped so that the Resource class can map your bean in this property whereas in the json, bean properties are at the same level as _links property. With this annotation, it is possible to have property name conflict with the wrapper and the inner bean. It is exactly the case here because Resource class has an id property inherited from the ResourceSupport class, and this property is sadly annotated by @JsonIgnore.

There is a workaround for this issue. You can create a new MixIn class inherited from the ResourceSupportMixin class and override the getId() method with @JsonIgnore(false) annotation :

public abstract class IdResourceSupportMixin extends ResourceSupportMixin {

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

Then you just have to add your IdResourceSupportMixin class to your ObjectMapper :

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

It should solve the problem.

Spoliate answered 12/6, 2015 at 20:23 Comment(4)
same problem with spring-hateoas and spring-boot, so I had to rename the id to something elseSpectrum
It was a year ago but if I remember correctly, it actually doesn't work because of a compilation error I think... The only one solution I found is cahen's one. You have to rename your id property to something else.Spoliate
This works for me: objectMapper.addMixIn(Resource.class, IdResourceSupportMixIn.class); - with jackson 2.8.8 and spring-hateoas 0.28, and rest template. But it breaks response.getBody().getId()Timothy
ResourceSupportMixin is package private. We can not extend it. But I have found solution. Use this. ``` 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<Link> getLinks(); } ```Hyperploid
V
1

With this code you find all @Entity beans an change the config to expose Id value:

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

 }
Virulent answered 10/4, 2017 at 15:36 Comment(1)
This solution worked for me. Thanks @Raul Villalba. I replaced the Example class with my BaseEntity where I defined private Long id and it worked. I also changed the class to public class SpringDataRestCustomization implements RepositoryRestConfigurer because RepositoryRestConfigurerAdapter is deprecated. docs.spring.io/spring-data/rest/docs/3.3.1.RELEASE/api/org/…Dyspepsia
G
0

This worked for me:

public class User extends ResourceSupport {

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

    // getters and setters
}

Also, change your http client to return PagedResources <User> instead of PagedResources<Resource<User>>

Gibber answered 23/12, 2016 at 17:0 Comment(2)
How did you have getter for Integer id field if ResourceSupport has public Link getId() method?Seduce
@MikhailKopylov You are right. @Will Franco - this solution doesn't work. This is the error that I see. 'getId()' in 'com.example.entity.User' clashes with 'getId()' in 'org.springframework.hateoas.ResourceSupport'; attempting to use incompatible return typeDyspepsia

© 2022 - 2024 — McMap. All rights reserved.