How to convert from json string to CqlDuration? - spring

I am trying to accept json data, and write into cassandra. One of the field is of type CqlDuration, in json, like:
{"down_duration": "1h"}
When I try to accept the data, convert into java class, I got error:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of `com.datastax.oss.driver.api.core.data.CqlDuration`
(no Creators, like default constructor, exist):
no String-argument constructor/factory method to deserialize from String value ('1h')
It seems Jackson failed to convert string "1h" to CqlDuration, should I add any Jackson annotation to solve this problem?

I just found that CqlDuration's constructor is private, and there's a public static method from can be used. so I added a setter:
public void setDownDuration(String downDuration) {
this.downDuration = CqlDuration.from(downDuration) ;
}
Problem solved.

Related

Deserializing a Enum with Jackson in Kotlin

I'm trying to serialize and deserialize an enum with Jackson.
My enum:
public enum class Type {
#JsonProperty("Typ A")
TypeA,
#JsonProperty("Typ B")
TypeB,
}
Serializing Type.TypeA results in the desired outcome of "Typ A". However Deserializing "Typ A" results in the following error:
java.lang.IllegalArgumentException: No enum constant de.advisori.pzp.task.TaskType.Typ A
I have tried other variations that I found online, such as this:
public enum class Type (#JsonValue val value: String) {
TypeA("Typ A"),
TypeB("Typ B"),
}
but they all yield the same result. Serialization works, deserialization results in the error above.
How do I correctly deserialize an enum with Jackson?
If it makes any difference: I am using it in a Spring Boot RequestMapping as a #RequestParam and return value.
As #dnault pointed out, Jackson isn't used for deserialization here. #RequestParams are never treated as JSON, hence Jackson is never used on them.
Two possible solutions are:
Using Kotlins ability to use spaces in names:
public enum class Type { `Typ A`, `Typ B` } (suggested by #DodgyCodeException)
Using a explicitly defined converter: https://stackoverflow.com/a/69031139/12898394 (pointed in the right direction by #Michal Ziober
I don't think any annotations will work to change the enum values. For this you need to write your own Serializer and Deserializer.
You will likely want to do this:
Create a Serializer by subclassing StdSerializer
Create a Deserializer by subclassing StdDeserializer
If you intend on using the enum as a key in JSON you will need KeyDeserializer too
Create a Module to wrap these up that you can pass to the configuration of Jackson, for that you use SimpleModule
There are many tutorials for this, e.g. https://www.baeldung.com/jackson-deserialization

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

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

What does Spring use to do deserialization?

I have the following class:
data class Thing(val lines: List<String>)
The JSON representation is:
{
"lines": [
"something",
"something else"
]
}
Spring WebFlux can successfully parse this with the following:
// Parse the JSON as an object and return it.
request -> ServerResponse.ok().body(request.bodyToMono(Thing::class.java)
However, using Jackson directly with either of the following techniques fails:
val mapper = ObjectMapper()
val item = mapper.readValue<Thing>("""{"lines":["something","something else"]}""")
ServerResponse.ok().body(request.bodyToMono(Map::class.java)
.map { map ->
val mapper = ObjectMapper()
val tmp = mapper.convertValue(map, Thing::class.java)
}
The error is:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `Thing` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
I thought that Spring was using Jackson to do its transformation. And I thought that Jackson could cope with such basic transformations from JSON -> POJOs. Using a #JsonDeserialize class obviously works. So why does the first example work and the second not?
Spring uses Jackson but it registers custom handlers and modules when it creates its default instance of ObjectMapper.
Jackson has special handling for constructors that take a single argument. This was done to support classes like UUID and URI. To instruct Jackson to not use this technique, annotate your constructor with #JsonCreator.
data class Thing #JsonCreator constructor(val lines: List<String>)
I have not reviewed Spring's reactive code so I do not know what or if it does something to disable Jackson's special handling.

Jackson deserializer priority?

I have a Spring Boot app that is modeling ActityStreams objects and for the most part Jackson's Polymorphic Deserialization works well.
There are 'objects' in the JSON which are references (links) and not JSON objects with type information. For instance
"actor":"https://some.actors.href/ rather than
"actor":{
"type":"Actor",
"name":"SomeActor"
}
I've written custom deserializers and and placed them on the fields to deal with this
#JsonDeserialize (using = ActorOrLinkDeserializer.class)
private Actor actor;
However my ActorOrLinkDeserializer is instantiated but never called and Jackson complains with Missing type id when trying to resolve subtype of [simple type, class org.w3.activity.streams.Actor]: missing type id property 'type' (for POJO property 'actor') which is from the polymorphic deserializer.
It appears that the polymorphic deserialization code takes precedence over my local #JsonDeserialize annotation and I need a way to force my code to run first.
I've tried using my own ObjectMapper rather than Boot's and there's no difference.
I'd appreciate pointers and suggestions.
It turns-out there's a fairly simple solution to this problem using a DeserializationProblemHandler.
What I've implemented that works for all test cases so far is
1.
objectMapper.addHandler(new DeserProblemHandler());
or register with Spring Boot.
2.
public class DeserProblemHandler extends DeserializationProblemHandler {
public JavaType handleMissingTypeId(DeserializationContext ctxt, JavaType baseType, TypeIdResolver idResolver, String failureMsg) {
return TypeFactory.defaultInstance().constructType(baseType.getRawClass());
}
}
Add a constructor to each of the polymorphic classes that takes a string argument which is the href.

Map JAX-RS #PathParam to POJO Constructor With Annotations

I want to create an endpoint which has a PathParam that automatically calls the constructor of an object to be injected, which has a constructor of a String argument. To spell it out in code:
Here is the resource
#GET
#Path("/{apiVersion}" + "/item")
public Response version(#PathParam("apiVersion") APIVersion apiVersion) {
return Response.ok().build();
}
I want the String to automatically be used in a call to the APIVersion constructor. In the APIVersion class
public APIVersion(String apiVersion) {
this.versionString = apiVersion;
}
Is it possible to do with only access to annotations? I do not have access to the ResourceConfig.
Yes, this is possible, without any annotations other than #PathParam, so the example you've given should work as-is. See https://jersey.github.io/documentation/latest/jaxrs-resources.html#d0e2271 (emphasis mine) :
In general the Java type of the method parameter may:
Be a primitive type;
Have a constructor that accepts a single String argument;
Have a static method named valueOf or fromString that accepts a single
String argument (see, for example, Integer.valueOf(String) and
java.util.UUID.fromString(String));
Have a registered implementation of
javax.ws.rs.ext.ParamConverterProvider JAX-RS extension SPI that
returns a javax.ws.rs.ext.ParamConverter instance capable of a "from
string" conversion for the type. or
Be List, Set or SortedSet, where T satisfies 2 or 3 above.
The resulting collection is read-only.

Resources