I am trying to understand whether it is possible to serialize a java Map to a Json response from Jersey.
Here is my service :
#Singleton
#Path("/")
public class ApiServiceResource {
#Inject
ISeedingUpdateService seedingUpdateService;
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/map")
public List<String> getMap() {
return newArrayList(seedingUpdateService.toString());
}
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/pojo")
public TemplateMessage getTemplateMessage(#PathParam("param") String param) {
return new TemplateMessage(param, seedingUpdateService.toString());
}
#XmlRootElement
public static class TemplateMessage {
public Map<String,String > param;
public TemplateMessage() {
}
public TemplateMessage(String param1, String param2) {
this.param = newHashMap();
this.param.put(param1,param2);
}
}
}
The getMap method fails since it cannot serialize the Map -->
SEVERE: The registered message body writers compatible with the MIME media type are:
application/json ->
com.sun.jersey.json.impl.provider.entity.JSONJAXBElementProvider$App
com.sun.jersey.json.impl.provider.entity.JSONArrayProvider$App
com.sun.jersey.json.impl.provider.entity.JSONObjectProvider$App
com.sun.jersey.json.impl.provider.entity.JSONRootElementProvider$App
com.sun.jersey.json.impl.provider.entity.JSONListElementProvider$App
*/* ->
And the second method works just fine the POJO is serialized with the Map inside.
Is there something that i am missing ?
By the way the app is configured in Guice , so here is the guice configuration :
#Override
protected void configureServlets() {
bind(ApiServiceResource.class);
/* bind jackson converters for JAXB/JSON serialization */
bind(MessageBodyReader.class).to(JacksonJsonProvider.class);
bind(MessageBodyWriter.class).to(JacksonJsonProvider.class);
Map<String,String> parameters = newHashMap();
parameters.put("com.sun.jersey.config.property.packages", "com.delver.update.api");
serve("/rest/*").with(GuiceContainer.class, parameters);
}
Instead of binding MessageBodyWriter (since you already have more than one) you could try what we do in our app, we bind the Jackson writer and exception mappers in Guice through:
bind(forName("com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider")).in(Scopes.SINGLETON);
bind(forName("com.fasterxml.jackson.jaxrs.json.JsonParseExceptionMapper")).in(Scopes.SINGLETON);
bind(forName("com.fasterxml.jackson.jaxrs.json.JsonMappingExceptionMapper")).in(Scopes.SINGLETON);
Related
I have a POST endpoint which accepts a JSON as request body.
#Path("/drink")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class DrinkResource {
#POST
public Drink getDrink(Fruit fruit) {
return new Drink(fruit.getName());
}
}
The request body is supposed to be deserialized into this POJO :
public class Fruit {
private final String name;
#JsonCreator
public Fruit(#JsonProperty("name") String name) {
this.name = name;
}
public String getName() {
return name;
}
}
I'm using Jackson for the deserialization.
Is it possible to make the deserialization fail when the JSON in the request body has duplicate keys ?
For example, if the request body looks like this : {"name" : "banana", "name" : "orange"}, I would like to get a 500 status code or another kind of error instead of having the json deserialized with the last property.
Basically, I'm looking for a solution with the same logic as the JsonParser.Feature.STRICT_DUPLICATE_DETECTION with the ObjectMapper but for a POST endpoint.
I'm also using quarkus so I don't know if there is a property for this. Something similar to quarkus.jackson.fail-on-unknown-properties=true but for the duplicate properties.
Add the following:
#Singleton
public class MyCustomizer implements ObjectMapperCustomizer {
#Override
public void customize(ObjectMapper objectMapper) {
objectMapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
}
}
If you do, the ObjectMapper that Quarkus uses will throw a JsonParseException thus leading to a HTTP 400 response.
I am using Jersey to implement RESTful webservice. Now the MediaType in which I return data is JSON.
#GET
#Produces({MediaType.APPLICATION_JSON })
public Response service() {
return Response
.ok(entity)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.build();
}
Here I set CONTENT_TYPE to json and my entity will be converted to json by Jersey framework.
Now I want to customize my json response.
Eg: I want to remove the empty elements or change the keys name of my Json object.
Default Jersey's Json conversion:
{
"cinter" : {
"state" : 1,
"checks" : 10,
}
}
What I want:
{
"cin" : {
"st" : 1,
"cs" : 10,
}
}
I know I can use Jackson library's my own ObjectMapper to customize my Json according to my needs.
But is this the standard way to do it if I want JSON conversion in a different way than Jersey's default conversion ??
Or can I change paramaters in Jersey's ObjectMapper ??
Should I be using my own ObjectMapper ?
Here is my thoughts about your options. First of all
So for every different response I should configure ObjectMapper
differently ?
If you want to use both json versions in different places like this
public Response getObject() // returns {"cinter" : {"state" : 1,"checks" : 10}}
public Response getShortNamesObject() // returns {"cin" : {"st" : 1,"cs" : 10}}
Than yep, you have to use multiple ObjectMappers.
But if you just want to use 1 representation everywhere, then you probably will be able to setup Jackson once with custom mixin for you classes. Anyway here is how you can do both options: And lets look at simple case with just 1 json version needed
public class TestBean {
private String name;
private int id;
//getters and setters
}
public interface TestBeanMixin {
#JsonProperty("short_field_name")
String getName();
#JsonProperty("short_field_id")
int getId();
}
#Provider
#Priority(1)
public class MixInJacksonJsonProvider extends JacksonJaxbJsonProvider {
private static final ObjectMapper mapper = createMapper();
public MixInJacksonJsonProvider() {
setMapper(mapper);
}
private static ObjectMapper createMapper() {
final ObjectMapper result = new ObjectMapper();
result.addMixIn(TestBean.class, TestBeanMixin.class);
return result;
}
}
This code will produce short names for you POJO fields everywhere. and to implement different behavior for different request we have to add new custom annotation like this:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public #interface MixIn {}
Controller will look like this:
#Path("test")
public class MyResource {
#GET
#MixIn // <== Here is important part
#Produces(MediaType.APPLICATION_JSON )
public Response getShortName() {
return Response.ok(demoObj()).build();
}
#POST
#Produces(MediaType.APPLICATION_JSON )
public Response postLongName() {
return Response.ok(demoObj()).build();
}
}
And our MixInJacksonJsonProvider will have 2 more #Override:
//.. same as before
#Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return super.isReadable(type, genericType, annotations, mediaType) && hasMixInAnnotation(annotations);
}
#Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return super.isWriteable(type, genericType, annotations, mediaType) && hasMixInAnnotation(annotations);
}
public static boolean hasMixInAnnotation(Annotation[] annotations){
for(Annotation annotation: annotations){
if (annotation instanceof MixIn){
return true;
}
}
return false;
}
}
Here is demo code for you to play: https://github.com/varren/jersey2-jacksonsetup/tree/master/src/main/java/ru/varren
I am writing a web service which should accept type Object[]. Its universal and needs to accept different number and types of parameters in different scenarios.
Request object looks like this:
#XmlRootElement
public class SimilarityRequest {
private Object[] params;
private String similarity;
public Object[] getParams() {
return params;
}
public void setParams(Object[] params) {
this.params = params;
}
public String getSimilarity() {
return similarity;
}
public void setSimilarity(String similarity) {
this.similarity = similarity;
}
}
This is WebService:
#SessionScoped
#Path("/similarity/")
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Stateful
#StatefulTimeout(600000) // 10 minutes
public class SimilarityResource {
#POST
#Path("/")
public List<SimilarityResult> universalSimilarity(JAXBElement<SimilarityRequest> sr) {
Object[] params = sr.getValue().getParams();
String similarity = sr.getValue().getSimilarity();
}
}
I dont know what json it accepts for params in this case? I tried "params":{5,10} and "params":{"0":5,"1":10} and also "params":[5,10]. Something throws 500 and something 400 (bad request). Any ideas?
I've successfully implemented the service using Jersey, the code is the same, I've just removed the JAXBElement wrapper and the #XmlRootElement annotation.
The WEB-INF.xml file must include the folder containing the SimilarityRequest class in the
com.sun.jersey.config.property.packages parameter section and the com.sun.jersey.api.json.POJOMappingFeature parameter must be true.
The service receives correctly the following json:
{ "similarity": "test", "params":[5,10] }
The object array contains two Integer values.
I am trying to create a rest api server on top of my quartz scheduler. I want to be able to return the org.quartz.Trigger and org.quartz.JobDetail objects as JSON. The problem is that I cannot add the #XmlRootElement to these classes without having to recompile the jar and this causes problems with future upgrades etc. I have tested and am able to serialize a list of classes when adding the #XmlRootElement but when I try to return a List I get the error "A message body writer for Java class java.util.ArrayList, and Java type java.util.List, and MIME media type application/json was not found". I have tried adding a custom MessageBodyWriter but that does not seem to fix the problem either. Is there a way to marshal the quartz classes to JSON without having to modify the quartz code to add the #XmlRootElement. I am using this in an embedded web server with jetty btw.
#Path("/jobs")
public class JobsResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Trigger> listScheduledJobs() throws SchedulerException {
return TaskEngine.getInstance().listScheduledJobs();
}
}
Web server configuration
public class TaskEngineWebServer {
private static final Logger logger = Logger.getLogger(TaskEngineWebServer.class.getName());
private Server server;
public TaskEngineWebServer() {
this(8585);
}
public TaskEngineWebServer(Integer port) {
server = new Server(port);
logger.info("Configuring rest service to start at url /r");
ServletContextHandler handler = new ServletContextHandler(ServletContextHandler.NO_SECURITY);
//handler.getInitParams().put("com.sun.jersey.api.json.POJOMappingFeature", "true");
PackagesResourceConfig packagesResourceConfig = new PackagesResourceConfig("com.hp.vf.scheduler.server.rest");
ServletContainer servletContainer = new ServletContainer(packagesResourceConfig);
handler.addServlet(new ServletHolder(servletContainer), "/r/*");
server.setHandler(handler);
logger.info("Done configuring rest service");
}
public void start() throws Exception {
server.start();
}
public void stop() throws Exception {
server.stop();
}
public boolean isStarted() {
return server.isStarted();
}
public boolean isStopped() {
return server.isStopped();
}
}
I dont think you can return a List as JSON directly. You need to have a wrapper class which contains this list. For eg try something like this
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class TriggerWrapper{
private List<Triggers> triggers;
public List<Triggers> getTriggers(){
if(triggers==null){
triggers = new ArrayList<Triggers>();
}
return triggers;
}
}
Then in your rest service class :
#Path("/jobs")
public class JobsResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
public TriggerWrapper listScheduledJobs() throws SchedulerException {
TriggerWrapper response = new TriggerWrapper();
List<Triggers> triggers = TaskEngine.getInstance().listScheduledJobs();
response.getTriggers.addAll(triggers);
return response;
}
}
Your json would something like this :
{
"triggerwrapper": {
"triggers": [
{
"triggerid": 1
},
{
"triggerid": 2
}
]
}
}
And ofcourse if you want you can drop the root element tag from your json its configurable in jersey.
I finally figured out a clean solution, it involves creating my own MediaBodyWriter class and adding it as a provider. You have to make sure you are not using the jersey-bundle jar as the default jaxb to json provider will override yours.
jars required
jersey-core
jersey-servlet
jersey-server
jackson-annotations
jackson-databind
jackson-core
I found this MediaWriter example on the web somewhere. Sorry for not having the url but thanks to whoever write it.
#Provider
#Produces({ MediaType.APPLICATION_JSON })
public class JacksonWriter implements MessageBodyWriter<Object> {
private static final ObjectMapper MAPPER = new ObjectMapper();
#Override
public boolean isWriteable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
return true;
}
#Override
public long getSize(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return -1;
}
#Override
public void writeTo(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) {
try {
MAPPER.writeValue(entityStream, value);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
When it loads you will see a log message that your provider was loaded.
This gave me the json output I was expecting as it does not rely on the JAXB annotations and simply uses the object mapper/ reflection. Probably less efficient but for my case it does not matter.
How can I specify a map as one of the parameters of a REST service e.g
#Path("/servicepath")
#Consumes(MediaType.APPLICATION_XML)
public class MyResource {
#POST
public Response getMap(Map<String, List<Object>>) {
//code here
}
}
or
#Path("/servicepath")
#Consumes(MediaType.APPLICATION_JSON)
public class MyResource {
#POST
public Response getMap(Map<String, List<Object>>) {
//code here
}
}
I am using Jersey. Should I implement a MessageBodyReader for that? But implementing a reader for a generic type like Map seems a bad way for me. Maybe I should write a wrapper class on top of the Map object.
What is your ideas? Thanks.
The JAX-RS specification (section 4.2.4) does require that implementors (like jersey) provide a MessageBodyReader implementation for MultivaluedMap<String, String> which is used to consume application/x-www-form-urlencoded mime types. So for example, you can do something like this:
#Path("/servicepath")
#POST
#Consumes("application/x-www-form-urlencoded")
#Produces("text/plain")
public String doTheFormThing(MultivaluedMap<String, String> formdata) {
return formdata.toString();
}
Is this not sufficient for what you're trying to do?