Spring4 MVC serialize enum as string with quotes - java

i have a rest controller in a spring boot mvc container
#RestController
public class LoginController {
#RequestMapping("rest/login")
public Response login() {
return Response.GRANTED;
}
public static enum Response {
GRANTED, DENIED;
}
}
I have to use double quotes for checking the return type after request a rest resource. how to avoid the double quotes?
$http.post("rest/login", $scope.data).success(function(data) {
if (data === "\"GRANTED\"") {
alert("GRANTED")
} else if (data === "DENIED") {
alert("DENIED")
};
#RestController
public class LoginController {
#RequestMapping("rest/login")
public String login() {
return Response.GRANTED.name();
}
public static enum Response {
GRANTED, DENIED;
}
}
bring the result I want but I want the type safe return type Response and not String.
Thanks for help.

A #RestController is like a #Controller annotated with #ResponseBody. That is, each handler is implicitly annotated with #ResponseBody. With any reference type other than String (and a few others), the default target content-type is JSON.
The 6 data types in JSON are Object, Array, Number, String, true, false, and null. How would you map an enum constant? The default that Jackson (which backs the default JSON HttpMessageConverter) serializes an enum constant to a JSON String. That's arguably the best matching JSON data type.
You could force it to write the value without quotes by providing your own JsonSerializer
#JsonSerialize(using = ResponseSerializer.class)
public static enum Response {
...
class ResponseSerializer extends JsonSerializer<Response> {
#Override
public void serialize(Response value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeRaw(value.name());
}
}
but I don't recommend it since you wouldn't be producing valid JSON.
You should really consider what others have suggested and use the various HTTP status codes.

Related

Accessing Quarkus query parameters from within Jackson serializer

I am currently trying to develop a solution within Quarkus that can serialize JSON responses dynamically based on the properties (i.e. query params, headers, etc.) of the associated request. Here's an example of what I am trying to achieve.
The user sends a GET request containing a query param lang which specifies the language.
#GET()
#Produces(MediaType.APPLICATION_JSON)
public Content getContent(
#QueryParam("lang")
#DefaultValue("en")
Language language
) {
// Fetch and return content from service
}
The Content class contains fields of type TranslatableString, which store keys used for lookups.
public #Value class TranslatableString {
String key;
}
The associated JsonSerializer should now take this custom type and use its key to provide the translation for the requested language.
#ApplicationScoped
public class Serializer extends JsonSerializer<TranslatableString> {
#Inject
TranslationStore translations;
#Override
public void serialize(TranslatableString value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(
translations.get(value.getKey(), /** ??-Language-?? **/ )
);
}
}
Is there some (easy) way to access the user-provided query parameters inside the Jackson JsonSerializer?
EDIT
This is how I register my custom serializer.
#Singleton
public class ObjectMapperConfig implements ObjectMapperCustomizer {
#Inject
Serializer serializer;
#Override
public void customize(ObjectMapper objectMapper) {
objectMapper.registerModule(
new SimpleModule()
.addSerializer(
TranslatableString.class,
serializer
)
);
}
}
Based on #RobSpoor's comment, I came up with the following solution using MapStruct and a DTO instead of relying on Jackson to do the translation.
Create domain specific and DTO class.
public #Value class Content {
TranslatableString body;
}
// Instances of Content will be mapped to ContentDto by using MapStruct
public #Value class ContentDto {
String body;
}
Create MapStruct mapper for mapping from domain object to DTO. Use MapStruct's Context feature to pass in language during mapping.
#Mapper(componentModel = "cdi")
public abstract class ContentMapper {
// Dummy translations
private final Map<Language, Map<String, String>> translations = Map.of(
Language.ENGLISH, Map.of(
"greeting", "Hello World"
),
Language.GERMAN, Map.of(
"greeting", "Hallo Welt"
)
);
// The language parameter will be passed to the translate method
abstract ContentDto toDto(Content content, #Context Language language);
// Use the language parameter to retrieve the right translation
protected String translate(TranslatableString translatableString, #Context Language language) {
return translations.get(language).get(translatableString.getKey());
}
}
Pass language into mapper during service call.
#Path("/greeting")
public class GreetingResource {
#Inject
ContentMapper mapper;
#GET
#Produces(MediaType.APPLICATION_JSON)
public ContentDto evaluate(
#QueryParam("lang")
#DefaultValue("en")
Language language
) {
return mapper.toDto(
new Content(new TranslatableString("greeting")),
language
);
}
}
Now /greeting can be called with an extra query parameter of lang specifying the desired language.

How can I instantiate a specific sub-type for a #RequestBody parameter based on the requested URI for a Spring MVC controller method?

Given the following basic domain model:
abstract class BaseData { ... }
class DataA extends BaseData { ... }
class DataB extends BaseData { ... }
I want to write a Spring MVC controller endpoint thus ...
#PostMapping(path="/{typeOfData}", ...)
ResponseEntity<Void> postData(#RequestBody BaseData baseData) { ... }
The required concrete type of baseData can be inferred from the typeOfData in the path.
This allows me to have a single method that can handle multiple URLs with different body payloads. I would have a concrete type for each payload but I don't want to have to create multiple controller methods that all do the same thing (albeit each would do very little).
The challenge that I am facing is how to "inform" the deserialization process so that the correct concrete type is instantiated.
I can think of two ways to do this.
First use a custom HttpMessageConverter ...
#Bean
HttpMessageConverter httpMessageConverter() {
return new MappingJackson2HttpMessageConverter() {
#Override
public Object read(final Type type, final Class<?> contextClass, final HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
// TODO How can I set this dynamically ?
final Type subType = DataA.class;
return super.read(subType, contextClass, inputMessage);
}
};
}
... which gives me the challenge to determine the subType based on the HttpInputMessage. Possibly I could use a Filter to set a custom header earlier when the URL is available to me, or I could use a ThreadLocal also set via a Filter. Neither sounds ideal to me.
My second approach would be to again use a Filter and this time wrap the incoming payload in an outer object which would then provide the type in a way that enables Jackson to do the work via #JsonTypeInfo. At the moment this is probably my preferred approach.
I have investigated HandlerMethodArgumentResolver but if I try to register a custom one it is registered AFTER the RequestResponseBodyMethodProcessor and that class takes priority.
Hmm, so after typing all of that out I had a quick check of something in the RequestResponseBodyMethodProcessor before posting the question and found another avenue to explore, which worked neatly.
Excuse the #Configuration / #RestController / WebMvcConfigurer mash-up and public fields, all for brevity. Here's what worked for me and achieved exactly what I wanted:
#Configuration
#RestController
#RequestMapping("/dummy")
public class DummyController implements WebMvcConfigurer {
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#interface BaseData {}
public static class AbstractBaseData {}
public static class DataA extends AbstractBaseData {
public String a;
}
public static class DataB extends AbstractBaseData {
public String b;
}
private final MappingJackson2HttpMessageConverter converter;
DummyController(final MappingJackson2HttpMessageConverter converter) {
this.converter = converter;
}
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(
new RequestResponseBodyMethodProcessor(Collections.singletonList(converter)) {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(BaseData.class)
&& parameter.getParameterType() == AbstractBaseData.class;
}
#Override
protected <T> Object readWithMessageConverters(
NativeWebRequest webRequest, MethodParameter parameter, Type paramType)
throws IOException, HttpMediaTypeNotSupportedException,
HttpMessageNotReadableException {
final String uri =
webRequest.getNativeRequest(HttpServletRequest.class).getRequestURI();
return super.readWithMessageConverters(
webRequest, parameter, determineActualType(webRequest, uri));
}
private Type determineActualType(NativeWebRequest webRequest, String uri) {
if (uri.endsWith("data-a")) {
return DataA.class;
} else if (uri.endsWith("data-b")) {
return DataB.class;
}
throw new HttpMessageNotReadableException(
"Unable to determine actual type for request URI",
new ServletServerHttpRequest(
webRequest.getNativeRequest(HttpServletRequest.class)));
}
});
}
#PostMapping(
path = "/{type}",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<? extends AbstractBaseData> post(#BaseData AbstractBaseData baseData) {
return ResponseEntity.ok(baseData);
}
}
The key to this is that I stopped using #RequestBody because that is what was preventing me overriding the built-in behaviour. By using #BaseData instead I get a HandlerMethodArgumentResolver that uniquely supports the parameter.
Other than that it was a case of assembling the two objects that already did what I needed, so autowire a MappingJackson2HttpMessageConverter and instantiate a RequestResponseBodyMethodProcessor with that one converter. Then pick the right method to override so that I could control what parameter type was used at a point that I had access to the URI.
Quick test. Given the following payload for both requests ...
{
"a": "A",
"b": "B"
}
POST http://localhost:8081/dummy/data-a
... gives a response of ...
{
"a": "A"
}
POST http://localhost:8081/dummy/data-b
... gives a response of ...
{
"b": "B"
}
In our real-world example this means that we will be able to write one method each that supports the POST / PUT. We need to build the objects and configure the validation possibly - or alternatively if we use OpenAPI 3.0 which we are investigating we could generate the model and validate without writing any further code ... but that's a separate task ;)

Adding custom object mappers with jackson and spring boot

I would like to be able to convert a generic Object in a request to a custom object that I have built using Spring Boot and Jackson.
For example, say I have the following controller method:
#RequestMapping(value = "/user/action", method = RequestMethod.POST)
public String processAction(#RequestBody Request theRequest){
return "Success";
}
Now the Request object looks like this:
public class Request {
public Request(){
}
private String action;
private Object actionRequest;
//getters/setters
The actual actionRequest can be any Object (e.g. Boolean, String, or a custom built one).
So say I have a custom built ActionA class:
public class ActionA {
public ActionA(){
}
private int actionAInfo;
//getters/setters
}
If I invoke my controller with a POST and a payload of
{"action": "startEstimate", "actionRequest":true}
when it reaches the method, the 'actionRequest' is already converted to a boolean.
However, if I provide the payload
{"action": "startEstimate", "actionRequest": {"actionAInfo": 5}}
the 'actionRequest' is just converted to a HashMap with key of 'actionAInfo' and value of '5'.
How can I configure Spring Boot/Jackson to create the ActionA object?
One workaround I have seen is to instead of having Object in the Request, use ObjectNode. Then do something similar to
ObjectMapper om = new ObjectMapper();
ActionA actionA = om.convertValue(theRequest.getActionRequest(), ActionA.class);
However, this does not expand as I add more actions because I would need to know the type before I attempt to build it.
I have also attempted a solution presented here
Custom JSON Deserialization with Jackson
and it does not seem to be working. I have created a new ActionADeserializer
public class ActionADeserializer extends JsonDeserializer<ActionA> {
#Override
public ActionA deserialize(JsonParser jp, DeserializationContext ctxt) throws
IOException,
JsonProcessingException {
return jp.readValueAs(ActionA.class);
}
}
and altered ActionA class to have
#JsonDeserialize(using = ActionADeserializer.class)
public class ActionA
When I submit the payload
{"action": "startEstimate", "actionRequest": {"actionAInfo": 5}}
it still creates the HashMap instead of the ActionA object. I set a breakpoint in the deserializer and do not even see it being invoked.

Serialize and deserialize an object with an abstract field using jackson

What I'd like to do seems simple, just handling an object like the following:
When receiving a Post request from a client(web browser), which has a JSON object in its body, the server deserializes it into a certain object, and serializes some of its fields then saves them into a database.
When receiving a Get request from the client, the server retrieves the object from the database, deserializes it, composes it with other information and gives it back to the client.
The problem is, the object contains an abstract field.
Saving a request works fine
When I add these annotations to the abstract class, the phase 1 works fine.
Request JSON:
{ config: { type: "concreteA", ...}, ...}
REST API:
#RequestMapping(value="/configs", method=RequestMethod.POST)
#ResponseBody
public ResponseEntity<Object> saveConfig(#RequestBody ConfigRequest request)
throws IOException {
...
}
ConfigRequest class:
public class ConfigRequest {
private AbstractConfig config;
// Abbr. other fields, and all getters and setters
}
AbstractConfig class, which is included in ConfigRequest
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME,
include=JsonTypeInfo.As.PROPERTY,
property="type",
visible=true)
#JsonSubTypes({#Type(value=ConcreteAConfig.class, name="concreteA")})
public abstract class AbstractConfig {
public AbstractConfig(){}
private String type;
// Abbr. other fields, and all getters and setters
}
Deserialized string:
{"type":"concreteA", ...}
Deserializing the json string fails
But when I try retrieving and deserializing(Phase 2), the deserialization fails:
16/03/24 17:17:20 ERROR (...Abbr...) org.codehaus.jackson.map.JsonMappingException: Can not construct instance of AbstractConfig, problem: abstract types can only be instantiated with additional type information
RowMapper, which raises the error:
public class BatchRowMapper implements RowMapper<Batch> {
private static ObjectMapper objectMapper = new ObjectMapper();
#Override
public Batch mapRow(ResultSet row, int rowNum) throws SQLException {
Batch batch = new Batch();
try {
// THIS METHOD RAISES THE ERROR
batch.setConfig(objectMapper.readValue(row.getString(CONFIG), AbstractConfig.class));
} catch (ClassCastException|IOException e) {
throw new SQLException(e.toString());
}
return batch;
}
}
I'd like to ser/de an abstract field with the same "type" field, and using only annotaions is my wish... Is it possible? If further information needed, I will be willing.
Thank you in advance.
I've found it's the problem what Jackson I used.
When I use jackson of codehause(older), deserialization with JsonTypeInfo doesn't work properly. Jackson of fasterxml works perfectly.

Spring MVC returning JSONS and exception Handling

I am using Spring MVC with Controllers, my question is how do I return a JSON response which is different from the #ResponseBody object which is returned and convereted to a JSON to be returned.
To elaborate further, I have the object called "UserDetails" which has two fields called "name", "emailAddress"
#ResponseBody UserDetails
now the json returned will look like
{ name : "TheUsersName",
emailAddress:"abc#abc123.com" }
Is there any way I can modify the json before returning (ALL jsons in all methods across all controllers) where a "status" field will be added and the other json data will be under the "data" key in the json.
Also how do I return a json to the frontend when the java server from somewhere throws an exception, the json should have "status : false" and the exception name (atleast the status part though)
Create a response class:
public class Response<T> {
T data;
boolean status = true;
public Response(T d) { data = d; }
}
Then return that from your controllers:
#ResponseBody public Response getUserDetails) {
//...
return new Response(userDetails);
}
For the exception you'll want to return an object like:
public class BadStatus {
String errorMessage;
boolean status = false;
public BadStatus(String msg) { errorMessage = msg; }
}
#ExceptionHandler(Exception.class)
public BadStatus handleException(Exception ex, HttpServletRequest request) {
return new BadStatus(ex.getMessage());
}
Yes. Return a model and a view instead.
public ModelMap getUserDetails() {
UserDetails userDetails; // get this object from somewhere
ModelMap map = new ModelMap()(;
map.addAttribute("data", userDetails);
map.addAttribute("success", true);
return map;
}
To add the exception you'd do it the same way with a key and success = false.
An alternate solution (works with spring 3.1), which is less invasive
in your spring config :
<bean id="jacksonConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="mypackage.MyMessageConverter"
p:delegate-ref="jacksonConverter">
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
The idea is to provide your own HttpMessageConverter that delegates to the provided jackson converter.
public class MyMessageConverter implements HttpMessageConverter<Object> {
// setters and delegating overrides ommitted for brevity
#Override
public void write(Object t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
// t is whatever your #ResponseBody annotated methods return
MyPojoWrapper response = new MyPojoWrapper(t);
delegate.write(response, contentType, outputMessage);
}
}
This way all your pojos are wrapped with some other json that you provide there.
For exceptions, the solution proposed by ericacm is the simplest way to go (remember to annotate the 'BadStatus' return type with #ResponseBody).
A caveat : your json-serialized BadStatus goes through MyMessageConverter too, so you will want to test for the object type in the overriden 'write' method, or have MyPojoWrapper handle that.

Categories