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
Related
I do have some entity class (code without annotations for simplified example)
class User {
public String id;
public String name;
}
Now I want to output this via an API, but I want to structure my response in a special format, like
{
"data": {
"id": 1,
"name": "mars3142"
}, // user object or another entity or list...
"meta": ...,
"error": ...
}
The meta and/or error data should only be visible in special situations (like RuntimeExceptions). Where is the best place to transform my entity results into the normalized response? Do I need to write a filter for that? Does anybody has a sample code for that?
I would suggest to implement something this:
public abstract class BaseResponse {
// Meta data
// Consider defining fields here needed for happy-path and error-responses
// Contains common tracking fields, e.g. correlationId, requestId
}
public class ErrorResponse extends BaseResponse {
// Error Fields
}
public class Response extends ErrorResponse {
// Entity-object in your case
}
I guess you can build your response like setting response from DAO to above suggested structure in controller layer. For error-responses (in case of RuntimeExceptions), they're standardly build and returned in #ControllerAdvice or other.
Some patterns of exception handling are explained in Error Handling for REST with Spring | Baeldung.
Regarding your 2 questions:
Design: The proper place for this response-mapping depends on the scope (all responses or just some) and existing components in your application's response layer.
Patterns and Web-Framework concepts: I would not use the response-filters or -interceptors of your web-framework. Those should be used for cross-cutting concerns, or for chained processes (e.g. security, authorization, enrichment, sanitation).
Instead I would use the web-frameworks concepts and components that are responsible for response-representations, like ResponseEntity (HTTP-response representation, ControllerAdvice (error-handling), HttpMessageConverter.
There are 3 ways you could "wrap" your objects into uniform JSON-response models:
Annotate class with the custom #JsonRootName as data and in special cases add meta and/or error attributes (through e.g. embedding into a wrapper or using a mixin)
A JSON custom serializer that could extend from BeanSerializer which wraps this and any class uniformly in your given outer structure
Modify Spring's MappingJackson2HttpMessageConverter to wrap any returned response object into the predefined JSON-structure
You could iterate from the simplest (1.) to the most complex (3.). Some iteration code (like 2.) can be reused in the next (3.).
1. Use a Wrapper Class
The first is rather a simple start where you can implement the "normalization" within controller-methods. You could for example put the object (serialized as data) into the "empty" meta-structure (wrapper-class) with an empty JsonNode, and meta or error properties.
2. Define a Custom Serializer
The second is pretty flexible and can be tested well in isolation (not even depending on Spring). It would allow to implement the complete object-wrapping in one place.
3. Customize Spring's HTTP Message Converter
The third is similar to the second but requires some knowledge about Spring's message-converters and allows you to transform each response-object to a specific JSON-response using Jackson's ObjectMapper.
Sample code can be found online, e.g. at Baeldung's Jackson or Spring tutorials, Springframework Guru articles.
I used the solution from https://stackoverflow.com/a/72355056/708157 and transformed it a little bit.
Now my classes are that way
public class BaseResponse<T> {
boolean success;
T data;
Error error;
}
public class Error {
...
}
And every api response is now ResponseEntity<BaseResponse<XYZ>>. This way, I can setup my default structure and my classes are lose coupled, because I can use every class for T within my BaseResponse.
I am new to Springboot reactive
I was asked to call the following endpoint and return todays weather data only:
https://api.weather.gov/gridpoints/MLB/33,70/forecast
I believe I need to use something like this...
WebClient.create().get()
.uri("https://api.weather.gov/gridpoints/MLB/33,70/forecast")
.retrieve()
.bodyToMono(WeatherClass.class)
.block();
Do I need to map out an entire java object to match the JSON at the endpoint? is there an easy way to perhaps just grab the a certain piece of the JSON?
How would I handle something like the #context annotation in the JSON.
The WebClient in spring boot automatically uses Jackson's ObjectMapper to unmarshall json to a java object when the content type of the response is application/json. So there is no need to pull in any additional libraries or have to write any specific unmarshalling code, unless you want to use an alternate json-to-java library.
When using Jackson, you don't need to map every field in the json to your java object. You can annotate your java class with #JsonIgnoreProperties to inform jackson to ignore any properties that may appear in the json but do not have a matching field in your java object.
An example WeatherClass in which you want only the #context and forecastGenerator unmarshalled would look something like this
#JsonIgnoreProperties
public class WeatherClass {
private final List<Object> context;
private final WeatherProperties weatherProperties;
public WeatherClass(#JsonProperty("#context") List<Object> context,
#JsonProperty("properties") WeatherProperties weatherProperties) {
this.context = context;
this.weatherProperties = weatherProperties;
}
private class WeatherProperties {
private final String forecastGenerator;
private WeatherProperties(#JsonProperty("forecastGenerator") String forecastGenerator) {
this.forecastGenerator = forecastGenerator;
}
}
}
Note
#context seems to be an array that can contain multiple types (both objects and strings in your example). I've used Object to work around this but obviously isn't the most graceful solution but should be adequate to demonstrate how Jackson works
Alternatively, you can unmarshall the response to a JsonNode, which you can then use to traverse the structure of the json without converting it to a java object. For example
String forecastGenerator = WebClient.create().get()
.uri("https://api.weather.gov/gridpoints/MLB/33,70/forecast")
.retrieve()
.bodyToMono(JsonNode.class)
.block().get("properties").get("forecastGenerator").toString()
There are many other annotations provided by Jackson that can used to define how the unmarshaller functions. Too many to cover here. See Jackson Deserialisation Annotations
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.
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.
I'm facing a problem with Spring and restTemplate. I want to send an object (ListResponse) that contains a generic array. The defenition is as follow:
public class ListResponse<T> implements Serializable {
private long total;
private int page;
private int pageSize;
private T[] objects;
I send a request whith restTemplate.getForObject(). As a result I get an object of type ListResponse but the objects array contains an array of LinkedHashMaps instead of an array with objects of type T.
It seems like restTemplate can not convert the elements in the array to their correct type.
How can I make sure that I get an array of objects of type T back ?
I had this problem today and here is the solution that I came up with (actually, that one of my co-workers suggested). We use it with an interface that returns List<MyDto>.
When you call the RestTemplate, don't pass in the generic type.
Define: public class MyDtoListTemplate extends ListTemplate<MyDto>
Then, call
MyDtoListTemplate template = restTemplate.getForObject("url", MyDtoListTemplate .class, mapOfPathVariables);
It's a bummer that you have to define a concrete class that extends/implements the generic type, but then the generic information is available to the jackson deserializer.
I remember I was able to deserialize generic classes with Jackson 2. I had to add MappingJackson2HttpMessageConverter converter to RestTemplate before making any Http calls with it.
RestTemplate template = new RestTemplate();
template.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
Unfortunately there's no easy way of doing it that I know of. The problem is that the RestTemplate is told which object type to expect. As long as all the fields in this object has a corresponding element in the json/xml, everything works fine. In the case of generics, the serializer doesn't know which class to expect so it just turns the map it gets to a java Map.
You will have the same problem if you tried to getForObject for a generic return type.