Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 518
mfalaize Avatar asked Jun 07 '15 15:06

mfalaize


People also ask

How do I ignore null values in JSON response Jackson?

You can ignore null fields at the class level by using @JsonInclude(Include. NON_NULL) to only include non-null fields, thus excluding any attribute whose value is null. You can also use the same annotation at the field level to instruct Jackson to ignore that field while converting Java object to json if it's null.

How does Jackson serialize null?

Serialize Null Fields Fields/PropertiesWith its default settings, Jackson serializes null-valued public fields. In other words, resulting JSON will include null fields. Here, the name field which is null is in the resulting JSON string.

How do I ignore Jsonproperty?

To ignore individual properties, use the [JsonIgnore] attribute. You can specify conditional exclusion by setting the [JsonIgnore] attribute's Condition property. The JsonIgnoreCondition enum provides the following options: Always - The property is always ignored.

What is ObjectMapper class in Jackson?

ObjectMapper is the main actor class of Jackson library. ObjectMapper class ObjectMapper provides functionality for reading and writing JSON, either to and from basic POJOs (Plain Old Java Objects), or to and from a general-purpose JSON Tree Model (JsonNode), as well as related functionality for performing conversions.


2 Answers

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

 }
like image 100
Raul Villalba Avatar answered Oct 21 '22 22:10

Raul Villalba


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.

like image 34
mfalaize Avatar answered Oct 21 '22 22:10

mfalaize