Spring Jackson ObjectMapper Fails to JSON Deserialize While the Unit Test Passes - spring

I've got a class in my Spring web application:
#Value // or #Data Lombok
public class Bar {
private final BigDecimal value;
#JsonCreator
public Bar(double value) {
this.value = BigDecimal.valueOf(value);
}
}
I wrote a unit test which passes:
#Test
void test() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
Bar bar = new Bar(12.34);
assertEquals(mapper.readValue("12.34", Bar.class), bar);
}
But when I send a POST to the controller, it fails to deserialize the request body (which is just 12.34 to be deserialized to a Bar instance) with the following error:
JSON parse error: Cannot construct instance of com.example.demo.Bar (although at least one Creator exists): no double/Double-argument constructor/factory method to deserialize from Number value (12.34); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of com.example.demo.Bar (although at least one Creator exists): no double/Double-argument constructor/factory method to deserialize from Number value (12.34)
If I remove the #Value, it can deserialize it. To make it even more confusing, if I add the constructor (created by #Value) manually, still it works. But if I remove #JsonCreator it again can deserialize it.
What am I missing?

#Jsoncreator should be used in conjunction with #JsonProperty, to specify how to deserialize a JSON object. So if for example you have a rest controller that some JSON like:
{
"value": 123
}
Your constructor should be annotated like such:
#JsonCreator
public Bar(#JsonProperty("value") double value) {
this.value = BigDecimal.valueOf(value);
}
Although this might see redundant, the idea is that this allows for more flexibily in cases were the JSON you intend to deserialize doesn't match the name of your properties.
So for example if the object you are receiving still has a key value, but your class has a property myValue, the following will work:
public class Example {
private final BigDecimal myValue;
#JsonCreator
public Bar(#JsonProperty("value") double myValue) {
this.myValue= BigDecimal.valueOf(myValue);
}
}

Related

Deserialising problem with lombok builder and jackson, cannot construct instance of [...] no String-argument constructor/factory method to deserialize

This is a simplified version of a problem I've been having. Given these classes:
#Value
#Jacksonized
#Builder(builderClassName = "Builder", setterPrefix = "with")
public class Limits {
Limit minimum;
Limit maximum;
}
#Value
#Jacksonized
#Builder(builderClassName = "Builder", setterPrefix = "with")
public class Limit {
#JsonValue
String value;
}
and this code:
Limits limits = Limits.builder()
.withMinimum(Limit.builder().withValue("One-Day").build())
.withMaximum(Limit.builder().withValue("One-Year").build())
.build();
System.out.println(objectMapper.writeValueAsString(limits));
it works as expected and gives me the following output:
{
"minimum": "One-Day",
"maximum": "One-Year"
}
However, when I try to deserialise the same JSON string, as follows:
String json = """
{"minimum":"One-Day","maximum":"One-Year"}
""";
objectMapper.readValue(json, Limits.class);
I get the following error:
Cannot construct instance of `Limit$Builder` (although at least one Creator exists):
no String-argument constructor/factory method to deserialize from String value ('One-Day')
at [Source: (String)"{"minimum":"One-Day","maximum":"One-Year"}"; line: 1, column: 12]
Is there a way to make it work without changing the data model or the JSON?
I tried adding #JsonCreator to the Builder of Limit as follows, but gives the same error
#Value
#Jacksonized
#Builder(builderClassName = "Builder", setterPrefix = "with")
public class Limit {
#JsonValue
String value;
public static final class Builder {
#JsonCreator
public Builder withValue(String value) {
this.value = value;
return this;
}
}
}
Appreciate any input on what I might be missing here.
Jackson does not support #JsonCreator in combination with builders. #JsonCreator basically says: The annotated method constructs a new instance, passing the JSON value as argument to its single parameter. That does not work for builders, because there is no such method that does it both (there are two separate methods, one for receiving a JSON value, and one for constructing the instance).
However, there is a simple workaround in your case without loosing immutability. Just put #JsonCreator on an all-args constructor as follows:
#Value
#AllArgsConstructor(onConstructor_ = {#JsonCreator})
public class Limit {
#JsonValue
String value;
}
You can keep the #Builder if you want to, but there is no need to do so.

How to link a Vaadin Grid with the result of Spring Mono WebClient data

This seems to be a missing part in the documentation of Vaadin...
I call an API to get data in my UI like this:
#Override
public URI getUri(String url, PageRequest page) {
return UriComponentsBuilder.fromUriString(url)
.queryParam("page", page.getPageNumber())
.queryParam("size", page.getPageSize())
.queryParam("sort", (page.getSort().isSorted() ? page.getSort() : ""))
.build()
.toUri();
}
#Override
public Mono<Page<SomeDto>> getDataByPage(PageRequest pageRequest) {
return webClient.get()
.uri(getUri(URL_API + "/page", pageRequest))
.retrieve()
.bodyToMono(new ParameterizedTypeReference<>() {
});
}
In the Vaadin documentation (https://vaadin.com/docs/v10/flow/binding-data/tutorial-flow-data-provider), I found an example with DataProvider.fromCallbacks but this expects streams and that doesn't feel like the correct approach as I need to block on the requests to get the streams...
DataProvider<SomeDto, Void> lazyProvider = DataProvider.fromCallbacks(
q -> service.getData(PageRequest.of(q.getOffset(), q.getLimit())).block().stream(),
q -> service.getDataCount().block().intValue()
);
When trying this implementation, I get the following error:
org.springframework.core.codec.CodecException: Type definition error: [simple type, class org.springframework.data.domain.Page]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.data.domain.Page` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (io.netty.buffer.ByteBufInputStream); line: 1, column: 1]
grid.setItems(lazyProvider);
I don't have experience with vaadin, so i'll talk about the deserialization problem.
Jackson needs a Creator when deserializing. That's either:
the default no-arg constructor
another constructor annotated with #JsonCreator
static factory method annotated with #JsonCreator
If we take a look at spring's implementations of Page - PageImpl and GeoPage, they have neither of those. So you have two options:
Write your custom deserializer and register it with the ObjectMapper instance
The deserializer:
public class PageDeserializer<T> extends StdDeserializer<Page<T>> {
public PageDeserializer() {
super(Page.class);
}
#Override
public Page<T> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
//TODO implement for your case
return null;
}
}
And registration:
SimpleModule module = new SimpleModule();
module.addDeserializer(Page.class, new PageDeserializer<>());
objectMapper.registerModule(module);
Make your own classes extending PageImpl, PageRequest, etc. and annotate their constructors with #JsonCreator and arguments with #JsonProperty.
Your page:
public class MyPage<T> extends PageImpl<T> {
#JsonCreator
public MyPage(#JsonProperty("content_prop_from_json") List<T> content, #JsonProperty("pageable_obj_from_json") MyPageable pageable, #JsonProperty("total_from_json") long total) {
super(content, pageable, total);
}
}
Your pageable:
public class MyPageable extends PageRequest {
#JsonCreator
public MyPageable(#JsonProperty("page_from_json") int page, #JsonProperty("size_from_json") int size, #JsonProperty("sort_object_from_json") Sort sort) {
super(page, size, sort);
}
}
Depending on your needs for Sort object, you might need to create MySort as well, or you can remove it from constructor and supply unsorted sort, for example, to the super constructor. If you are deserializing from input manually you need to provide type parameters like this:
JavaType javaType = TypeFactory.defaultInstance().constructParametricType(MyPage.class, MyModel.class);
Page<MyModel> deserialized = objectMapper.readValue(pageString, javaType);
If the input is from request body, for example, just declaring the generic type in the variable is enough for object mapper to pick it up.
#PostMapping("/deserialize")
public ResponseEntity<String> deserialize(#RequestBody MyPage<MyModel> page) {
return ResponseEntity.ok("OK");
}
Personally i would go for the second option, even though you have to create more classes, it spares the tediousness of extracting properties and creating instances manually when writing deserializers.
There are two parts to this question.
The first one is about asynchronously loading data for a DataProvider in Vaadin. This isn't supported since Vaadin has prioritized the typical case with fetching data straight through JDBC. This means that you end up blocking a thread while the data is loading. Vaadin 23 will add support for doing that blocking on a separate thread instead of keeping the UI thread blocked, but it will still be blocking.
The other half of your problem doesn't seem to be directly related to Vaadin. The exception message says that the Jackson instance used by the REST client isn't configured to support creating instances of org.springframework.data.domain.Page. I don't have direct experience with this part of the problem, so I cannot give any advice on exactly how to fix it.

Type casting in spring program

I am facing issue in spring program, the issue is Why type casting is applied in spring application?
below is the program for reference.
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Hello obj= (Hello)context.getBean("hello");
obj.getMessge();
}
}
The getBean(String) method returns an object of the type Object. Since you know that the method you annotated with #Bean(name = "hello") returns an instance of Hello you can safely cast getBean's return value to Hello.
Object getBean(String name) returns an instance of type Object; hence, you must cast it to whatever you expect it to return.
<T> T getBean(String name, Class<T> requiredType) overloaded method can be used alternatively, and it will return the object of type T.
You can change your code as follows:
Hello obj = context.getBean("hello", Hello.class);
and in this case, you will not need to cast the returned object explicitly.

Not Able to access getter of object returned from mocked method

TEST METHOD
public void saveDemandCenterCategory() {
String category = "testCategory";
DemandCenterCategoryEntity demandCenterCategoryEntity = new DemandCenterCategoryEntity();
demandCenterCategoryEntity.setId(1l);
demandCenterCategoryEntity.setCategory(category);
AgentEntity agentEntity = new AgentEntity();
agentEntity.setId(11l);
when(agentRepository.findByMobile(anyString())).thenReturn(agentEntity);
when(demandCenterCategoryEntityRepository.save(any(DemandCenterCategoryEntity.class))).
thenReturn(demandCenterCategoryEntity);
assertEquals(demandCenterServiceImpl.saveDemandCenterCategory(anyString(),any()),isNotNull());
}
Method to test
public DemandCenterCategoryEntity saveDemandCenterCategory(#NotEmpty String name,
#NotNull Entitlements entitlements) {
DemandCenterCategoryEntity demandCenterCategoryEntity = new DemandCenterCategoryEntity();
demandCenterCategoryEntity.setCategory(name);
demandCenterCategoryEntity.setUpdatedBy(agentRepository.findByMobile(entitlements.getSubject()).getId());//null pointer
return demandCenterCategoryEntityRepository.save(demandCenterCategoryEntity);
}
Getting Null pointer expression while getting data from mocked method returned data.
It is probably the any() passed as entitlements argument that causes the NPE
By the way you should not use mockito matchers in the method under test

How do you handle deserializing empty string into an Enum?

I am trying to submit a form from Ext JS 4 to a Spring 3 Controller using JSON. I am using Jackson 1.9.8 for the serialization/deserialization using Spring's built-in Jackson JSON support.
I have a status field that is initially null in the Domain object for a new record. When the form is submitted it generates the following json (scaled down to a few fields)
{"id":0,"name":"someName","status":""}
After submitted the following is seen in the server log
"nested exception is org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.blah.domain.StatusEnum from String value '': value not one of the declared Enum instance names"
So it appears that Jackson is expecting a valid Enum value or no value at all including an empty string. How do I fix this whether it is in Ext JS, Jackson or Spring?
I tried to create my own ObjectMapper such as
public class MyObjectMapper extends Object Mapper {
public MyObjectMapper() {
configure(DeserializationConfig.Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
}
}
and send this as a property to MappingJacksonMappingView but this didn't work. I also tried sending it in to MappingJacksonHttpMessageConverter but that didn't work. Side question: Which one should I be sending in my own ObjectMapper?
Suggestions?
The other thing you could do is create a specialized deserializer (extends org.codehaus.jackson.map.JsonDeserializer) for your particular enum, that has default values for things that don't match. What I've done is to create an abstract deserializer for enums that takes the class it deserializes, and it speeds this process along when I run into the issue.
public abstract class EnumDeserializer<T extends Enum<T>> extends JsonDeserializer<T> {
private Class<T> enumClass;
public EnumDeserializer(final Class<T> iEnumClass) {
super();
enumClass = iEnumClass;
}
#Override
public T deserialize(final JsonParser jp,
final DeserializationContext ctxt) throws IOException, JsonProcessingException {
final String value = jp.getText();
for (final T enumValue : enumClass.getEnumConstants()) {
if (enumValue.name().equals(value)) {
return enumValue;
}
}
return null;
}
}
That's the generic class, basically just takes an enum class, iterates over the values of the enum and checks the next token to match any name. If they do it returns it otherwise return null;
Then If you have an enum MyEnum you'd make a subclass of EnumDeserializer like this:
public class MyEnumDeserializer extends EnumDeserializer<MyEnum> {
public MyEnumDeserializer() {
super(MyEnum.class);
}
}
Then wherever you declare MyEnum:
#JsonDeserialize(using = MyEnumDeserializer.class)
public enum MyEnum {
...
}
I'm not familiar with Spring, but just in case, it may be easier to handle that on the client side:
Ext.define('My.form.Field', {
extend: 'Ext.form.field.Text',
getSubmitValue: function() {
var me = this,
value;
value = me.getRawValue();
if ( value === '' ) {
return ...;
}
}
});
You can also disallow submitting empty fields by setting their allowBlank property to false.
Ended up adding defaults in the EXT JS Model so there is always a value. Was hoping that I didn't have to this but it's not that big of a deal.
I have the same issue. I am reading a JSON stream with some empty strings. I am not in control of the JSON stream, because it is from a foreign service. And I am always getting the same error message. I tried this here:
mapper.getDeserializationConfig().with(DeserializationConfig.Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
But without any effect. Looks like a Bug.

Resources