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.
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.
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.
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.
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.
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;
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With