Given a class like this:
#XmlRootElement
public class MyClass {
private Boolean flag1;
private String json;
...
}
Can I add an annotation that will cause the JSON to become part of the rendered JSON without being escaped as a string?
For example, if the object has flag1=true and json="{"a":5}" I want to get:
{"flag":true,"json":{"a":5}}
instead of:
{"flag":true,"json":"{\"a\":5}"}
You can write your own marshaller to do so.
You can use a Provider to change default behaviour of JSONJAXBContext. See http://jersey.java.net/nonav/documentation/latest/user-guide.html#d4e865
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.api.json.JSONJAXBContext;
#Provider
public class CustomWoodwingOutputJSONContextProvider implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class<?>[] types = { MyClass.class };
public CustomWoodwingOutputJSONContextProvider() throws JAXBException {
this.context = new JSONJAXBContext(JSONConfiguration.natural().build(), types);
}
public JAXBContext getContext(Class<?> objectType) {
for (int i = 0; i < this.types.length; i++)
if (this.types[i].equals(objectType))
return context;
return null;
}
}
Related
I'm working on a Spring application and I'd like to know if there's any way I could specify in my configuration the path of an XML file, having it automatically unmarshalled into a Java object through JAXB (I may consider other libraries though) and then inject it into a bean.
A Google search yields different results but they seem more about injecting a marshaller/unmarshaller in your bean and then doing the work yourself (like this one https://www.intertech.com/Blog/jaxb-tutorial-how-to-marshal-and-unmarshal-xml/) and I'm more interested in delegating this boilerplate to Spring.
Thanks
You can implement your custom resource loader based on this article: Spicy Spring: Create your own ResourceLoader. It requires some assumptions:
Classes you want to load have all required annotation used by JAXB which allow deserialisation.
You can build JaxbContext using given list of classes.
You need to check yourself whether loaded class is what you expect.
Step 0 - create POJO
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement(name = "User")
#XmlAccessorType(XmlAccessType.FIELD)
public class User {
#XmlElement(name = "firstName")
private String firstName;
#XmlElement(name = "lastName")
private String lastName;
// getters, setters, toString
}
You need to predefine POJO model which will be loaded from XML files. Above example just present one class but it should be similar for all other POJO classes.
Step 1 - create unmarshaller
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
#Component
public class JaxbResourceUnmarshaller {
private JAXBContext context;
public JaxbResourceUnmarshaller() {
try {
context = JAXBContext.newInstance(User.class);
} catch (JAXBException e) {
throw new IllegalArgumentException(e);
}
}
public Object read(Resource resource) {
try {
Unmarshaller unmarshaller = context.createUnmarshaller();
return unmarshaller.unmarshal(resource.getInputStream());
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
}
Simple unmarshaller implementation where you need to create JAXBContext. You need to provide all root classes.
Step 2 - create class resource
import org.springframework.core.io.AbstractResource;
import java.io.IOException;
import java.io.InputStream;
public class ClassResource extends AbstractResource {
private final Object instance;
public ClassResource(Object instance) {
this.instance = instance;
}
public Object getInstance() {
return instance;
}
#Override
public String getDescription() {
return "Resource for " + instance;
}
#Override
public InputStream getInputStream() throws IOException {
return null;
}
}
I could not find any specific class which could allow to return POJO instance. Above class has simple job to transfer class from deserialiser to Spring bean. You can try to find better implementation or improve this one if needed.
Step 3 - create JAXB resource loader
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
public class JaxbResourceLoader implements ResourceLoader {
private static final String DB_URL_PREFIX = "jaxb:";
private final ApplicationContext applicationContext;
private final ResourceLoader delegate;
public JaxbResourceLoader(ApplicationContext applicationContext, ResourceLoader delegate) {
this.applicationContext = applicationContext;
this.delegate = delegate;
}
#Override
public Resource getResource(String location) {
if (location.startsWith(DB_URL_PREFIX)) {
JaxbResourceUnmarshaller unmarshaller = this.applicationContext.getBean(JaxbResourceUnmarshaller.class);
String resourceName = location.replaceFirst(DB_URL_PREFIX, "");
Resource resource = applicationContext.getResource("classpath:" + resourceName);
Object instance = unmarshaller.read(resource);
return new ClassResource(instance);
}
return this.delegate.getResource(location);
}
#Override
public ClassLoader getClassLoader() {
return this.delegate.getClassLoader();
}
}
In case resource definition starts from jaxb: let's try to handle it. In other case postpone to default implementation. Only classpath resources are supported.
Step 4 - register JAXB resource loader
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.Ordered;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;
#Component
public class ResourceLoaderBeanPostProcessor implements BeanPostProcessor, BeanFactoryPostProcessor, Ordered,
ResourceLoaderAware, ApplicationContextAware {
private ResourceLoader resourceLoader;
private ApplicationContext applicationContext;
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.resourceLoader);
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
this.resourceLoader = new JaxbResourceLoader(this.applicationContext, this.resourceLoader);
beanFactory.registerResolvableDependency(ResourceLoader.class, this.resourceLoader);
}
#Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
#Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
}
This is just a copy of register class from article with only some changes. Probably could be much improved with latest Spring version.
Step 5 - simple usage
Assume you have pojos/user.xml file in resource folder which looks like below:
<User>
<firstName>Rick</firstName>
<lastName>Bartez</lastName>
</User>
You can inject it into Spring context like below:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
#Configuration
public class JaxbAwareConfiguration {
#Bean
public AppOwner appOwner(ResourceLoader resourceLoader) {
ClassResource resource = (ClassResource) resourceLoader.getResource("jaxb:pojos/user.xml");
User user = (User) resource.getInstance();
return new AppOwner(user);
}
}
A little bit unpleasant is casting resource to ClassResource and instance to User class but it is a downside of this solution.
I am facing some issue with REST POST call. I am consuming JSON object.
Lets suppose I have 2 classes below
Class Parent extends Serializable
{
private static final long serialVersionUID = 1L;
#Key
protected String Market;
#Key
protected String Symbol;
}
Class Child extends Parent
{
private static final long serialVersionUID = -4252878751127065794L;
private Double strikePrice;
private String optionType;
}
#POST
#Path("/addProduct")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response addProduct(Child child) {
--somecode--
}
Now I am passing JSON through Postman with all the properties which include all the properties from Parent class also. But when I am debugging this child object it is not giving parent properties. Do I need to use GSON or any other lib for this ?
JSON
{
"Market": "BSE",
"Symbol": "Infosys",
"strikePrice": 100,
"optionType": "Put"
}
I can see only strikePrice and optionType with data.
I found one solution. We need to add MessageBodyReader for Custom Response.
I dont know the best practice but it will work without issue.
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Scanner;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.Providers;
import common.SerializeUtil;
#Provider
public class CustomMessageBodyReader implements MessageBodyReader<Child> {
#Context
private Providers providers;
#Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
// TODO Auto-generated method stub
return Parent.class.isAssignableFrom(type);
}
#Override
public Child readFrom(Class<Child> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
throws IOException, WebApplicationException {
String entity = toString(entityStream);
Child child = SerializeUtil.fromString(entity, Child.class);
return child;
}
public static String toString(InputStream inputStream) {
return new Scanner(inputStream, "UTF-8")
.useDelimiter("\\A").next();
}
}
You also need to register this to your service config like below
ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.register(CustomMessageBodyReader.class);
Please feel free to provide best solution.
I'm working in a legacy product that heavily relies on version 1 of the org.jdom project (http://mvnrepository.com/artifact/org.jdom/jdom/1.1.3) and manually constructed XMLs but I would like to use Jaxb as much as possible instead. We're using Moxy as the Jaxb implementation.
So say that I have the following xml:
<foo bar="bar">
<baz>
<test value="something" />
</baz>
</foo>
And because of legacy code using the baz element as an org.jdom.Element I would like to have Jaxb unmarshall the inner element "baz" to an org.jdom.Element but v in unmarshal is always an empty string ("") so baz becomes null.
I've created an example below where I try to use an XmlAdapter but I can't get it to work.
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.transform.stream.StreamSource;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
public class Test {
public static void main(String[] args) throws JAXBException {
String fooString = "<foo bar=\"bar\"><baz><test value=\"something\" /></baz></foo>";
JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
.createContext(new Class<?>[] {Foo.class}, null);
javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Foo foo = unmarshaller
.unmarshal(new StreamSource(new ByteArrayInputStream(fooString.getBytes(StandardCharsets.UTF_8))), Foo.class)
.getValue();
System.out.println(foo);
}
#XmlRootElement
private static final class Foo {
#XmlAttribute
public String bar;
#XmlElement
#XmlJavaTypeAdapter(ElementAdapter.class)
public Element baz;
#Override
public String toString() {
return "Foo [bar=" + bar + ", baz=" + baz + "]";
}
}
private static final class ElementAdapter extends XmlAdapter<String, Element> {
#Override
public Element unmarshal(String v) throws Exception {
Document document = new SAXBuilder().build(new StringReader(v));
return document.getRootElement();
}
#Override
public String marshal(Element v) throws Exception {
return new XMLOutputter(org.jdom.output.Format.getPrettyFormat()).outputString(v);
}
}
}
Maybe I'm attacking this from the wrong angle. Any suggestions on how to achieve this?
A good way to debug issues when unmarshalling is to use the javax.xml.bind.helpers.DefaultValidationEventHandler.
In your case, you can simply add it to your unmarshaller in the main method, e.g.
// ...
javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler());
// ...
When you run your test program as-is with the validation handler attached, you'll see something like the following, which is basically telling you you have no Java class corresponding to the test element in your XML.
[Exception [EclipseLink-25004] (Eclipse Persistence Services - 2.6.1.v20150916-55dc7c3): org.eclipse.persistence.exceptions.XMLMarshalException
Exception Description: An error occurred unmarshalling the document
Internal Exception: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 47; unexpected element (uri:"", local:"test"). Expected elements are (none)]
I think you are assuming that your ElementAdapter implementation will read & consume the baz node and all children as a string. In actuality, I think (paging Blaise Doughan?) what's happening is that JAXB is doing some pre-walking of the XML tree, during which it sees that you have no model for test and subsequently discards test and its attribute.
To get a feel for what is going on here, first simplify your model and don't worry about the JDOM Element conversion. Note that I've changed the name of your top level class so it doesn't conflict with the Test model class:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.transform.stream.StreamSource;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) throws JAXBException {
String fooString = "<foo bar=\"bar\"><baz><test value=\"something\" /></baz></foo>";
JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
.createContext(new Class<?>[] {Foo.class}, null);
javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler());
Foo foo = unmarshaller
.unmarshal(new StreamSource(new ByteArrayInputStream(fooString.getBytes(StandardCharsets.UTF_8))), Foo.class)
.getValue();
System.out.println(foo);
}
#XmlRootElement()
public static class Foo {
#XmlAttribute
public String bar;
#XmlElement
public Baz baz;
#Override
public String toString() {
return "<foo bar=\"" + bar + "\">" + baz.toString() + "</foo>";
}
}
#XmlRootElement
public static class Baz {
#XmlElement
public Test test;
#Override
public String toString() {
return "<baz>" + test.toString() + "</baz>";
}
}
#XmlRootElement
public static class Test {
#XmlAttribute
public String value;
#Override
public String toString() {
return "<test value=\"" + value + "\"/>";
}
}
}
This should unmarshal correctly and print <foo bar="bar"><baz><test value="something"/></baz></foo>. Also note that normally you would pass the JAXBContextFactory an ObjectFactory containing the entire object graph, which would be generated from your XML Schema. This may be a source of confusion.
Now you can add your XmlAdapter back, but instead of attempting to convert from String to Element, convert from Baz to Element:
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.transform.stream.StreamSource;
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) throws JAXBException {
String fooString = "<foo bar=\"bar\"><baz><test value=\"something\" /></baz></foo>";
JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
.createContext(new Class<?>[] {Foo.class}, null);
javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler());
Foo foo = unmarshaller
.unmarshal(new StreamSource(new ByteArrayInputStream(fooString.getBytes(StandardCharsets.UTF_8))), Foo.class)
.getValue();
System.out.println(foo);
}
#XmlRootElement()
public static class Foo {
#XmlAttribute
public String bar;
#XmlElement
#XmlJavaTypeAdapter(ElementAdapter.class)
public Element baz;
#Override
public String toString() {
return "<foo bar=\"" + bar + "\">" + baz.toString() + "</foo>";
}
}
#XmlRootElement
public static class Baz {
#XmlElement
public Test test;
#Override
public String toString() {
return "<baz>" + test.toString() + "</baz>";
}
}
#XmlRootElement
public static class Test {
#XmlAttribute
public String value;
#Override
public String toString() {
return "<test value=\"" + value + "\"/>";
}
}
public static class ElementAdapter extends XmlAdapter<Baz, Element> {
#Override
public Element unmarshal(Baz baz) throws Exception {
StringWriter sw = new StringWriter();
// Note: it is a terrible idea to re-instantiate a context here
// Use a cached value or a singleton from before
JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
.createContext(new Class<?>[] {Baz.class}, null);
javax.xml.bind.Marshaller m = jaxbContext.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.marshal(baz, sw);
Document document = new SAXBuilder().build(new StringReader(sw.toString()));
return document.getRootElement();
}
#Override
public Baz marshal(Element v) throws Exception {
// TODO implement this
return null;
}
}
}
This should more or less do what you want, though you may have to clean up the Baz element some in the adapter (get rid of the XML preamble, etc.) before converting to JDOM. Do note that instantiating another JAXBContext in the ElementAdapter is probably a terrible idea, and there may be a more efficient way to convert from a JAXB Element to a JDOM Element. This code is just easy to step through in a debugger so you can see what's going on.
Thought I would share a solution that worked for me.
Inspired by this post Jaxb: how to unmarshall xs:any XML-string part? that mentions that Jaxb leaves anything it doesn't know how to map as DOM element when marked as #XmlAnyElement(lax = true) I ended up with an XmlAdapter using org.w3c.dom.Element as an intermediate class that Jaxb knows how to map to and from.
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.transform.stream.StreamSource;
import org.jdom.Element;
import org.jdom.output.DOMOutputter;
import org.jdom.output.XMLOutputter;
import org.w3c.dom.Document;
public class Test {
public static void main(String[] args) throws JAXBException {
String fooString = "<foo bar=\"bar\"><baz><test value=\"something\" /></baz></foo>";
JAXBContext jaxbContext = org.eclipse.persistence.jaxb.JAXBContextFactory
.createContext(new Class<?>[] {Foo.class}, null);
javax.xml.bind.Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Foo foo = unmarshaller
.unmarshal(new StreamSource(new ByteArrayInputStream(fooString.getBytes(StandardCharsets.UTF_8))), Foo.class)
.getValue();
System.out.println(foo);
}
#XmlRootElement
private static final class Foo {
#XmlAttribute
public String bar;
#XmlElement
#XmlJavaTypeAdapter(ElementAdapter.class)
public Element baz;
#Override
public String toString() {
return "Foo [bar=" + bar + ", baz=" + jdomElementToString(baz) + "]";
}
private String jdomElementToString(Element element) {
return new XMLOutputter(org.jdom.output.Format.getPrettyFormat()).outputString(element);
}
}
private static final class ElementAdapter extends XmlAdapter<org.w3c.dom.Element, Element> {
#Override
public Element unmarshal(org.w3c.dom.Element valueToUnmarshal) throws Exception {
org.jdom.input.DOMBuilder domBuilder = new org.jdom.input.DOMBuilder();
org.jdom.Element jdomElement = domBuilder.build(valueToUnmarshal);
return jdomElement;
}
#Override
public org.w3c.dom.Element marshal(Element elementToMarshal) throws Exception {
org.jdom.Document jdomDocument = new org.jdom.Document((Element) elementToMarshal.detach());
DOMOutputter domOutputter = new DOMOutputter();
Document domDocument = domOutputter.output(jdomDocument);
return domDocument.getDocumentElement();
}
}
}
The print out from running this is:
Foo [bar=bar, baz=<baz>
<test value="something" />
</baz>]
And as you can see what is in the baz tag is assigned "as-is" to the variable in the Foo class.
I am trying to create a simple web service which outputs using json, but am not getting the desired Json output.
POJO:
package com.rest.resource;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Track implements Serializable
{
#XmlElement
String singer = "ABC";
#XmlElement
String title = "XYZ";
}
Service:
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.JAXBException;
import com.rest.resource.Track;
#Path("/json/metallica")
public class JSONService
{
#POST
#Path("/post")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Track createTrackInJSON(final Track track)
{
return track;
}
#GET
#Path("/get")
#Produces(MediaType.APPLICATION_JSON)
public Response getTrackInJSON() throws JAXBException
{
final Track track = new Track();
return Response.status(201).entity(track).build();
}
}
On /get I get
{"singer":"ABC","title":"XYZ"}
but I want "track": {"singer":"ABC","title":"XYZ"}
I am unable yo print the root element.
I tried using a CustomJAXBContextResolver class but did not work for me? Can anyone give an example of the same?
If you want to use the ContextResolver, you'd need to use the JSONConfiguration and switch the JSON Notation. You could do that by adding a class like this:
#Provider
public class MyJAXBContextProvider implements ContextResolver<JAXBContext> {
private JSONJAXBContext trackCtx;
public MyJAXBContextProvider() throws JAXBException {
trackCtx = new JSONJAXBContext(JSONConfiguration.mappedJettison().build(), Track.class);
}
public JAXBContext getContext(Class<?> type) {
if(type == Track.class) {
return trackCtx;
}
return null;
}
}
Adding that class produced this for me:
{"track":{"singer":"ABC","title":"XYZ"}}
For more info check out the Jersey Docs
You'd have to wrap Track with another object:
public class TrackWrapper {
Track track;
TrackWrapper(Track track) {
this.track=track;
}
}
and return an instance of TrackWrapper,
#GET
#Path("/get")
#Produces(MediaType.APPLICATION_JSON)
public Response getTrackInJSON() throws JAXBException
{
final TrackWrapper trackWrapper = new TrackWrapper(new Track());
return Response.status(201).entity(trackWrapper).build();
}
}
and just in case, if you're gonna use JSON only you don't need the JAXB annotations.
in Java when i use the
#Produces("application/json")
annotation the output is not formated into human readable form. How do i achive that?
Just for the record, if you want to enable the pretty output only for some resources you can use the #JacksonFeatures annotation on a resource method.
Here is example:
#Produces(MediaType.APPLICATION_JSON)
#JacksonFeatures(serializationEnable = { SerializationFeature.INDENT_OUTPUT })
public Bean resource() {
return new Bean();
}
This is how you can properly do conditional pretty/non-pretty json output based on presence of "pretty" in query string.
Create a PrettyFilter that implements ContainerResponseFilter, that will be executed on every request:
#Provider
public class PrettyFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext reqCtx, ContainerResponseContext respCtx) throws IOException {
UriInfo uriInfo = reqCtx.getUriInfo();
//log.info("prettyFilter: "+uriInfo.getPath());
MultivaluedMap<String, String> queryParameters = uriInfo.getQueryParameters();
if(queryParameters.containsKey("pretty")) {
ObjectWriterInjector.set(new IndentingModifier(true));
}
}
public static class IndentingModifier extends ObjectWriterModifier {
private final boolean indent;
public IndentingModifier(boolean indent) {
this.indent = indent;
}
#Override
public ObjectWriter modify(EndpointConfigBase<?> endpointConfigBase, MultivaluedMap<String, Object> multivaluedMap, Object o, ObjectWriter objectWriter, JsonGenerator jsonGenerator) throws IOException {
if(indent) jsonGenerator.useDefaultPrettyPrinter();
return objectWriter;
}
}
}
And pretty much that's it!
You will need to ensure that this class gets used by Jersey by either automated package scanning or registered manually.
Spent few hours trying to achieve that and found that no-one has published a ready-to-use solution before.
Create this class anywhere in your project. It will be loaded on deployment. Notice the .configure(SerializationConfig.Feature.INDENT_OUTPUT, true); which configures the mapper to format the output.
For Jackson 2.0 and later, replace the two .configure() lines with these:
.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
.configure(SerializationFeature.INDENT_OUTPUT, true);
And change your imports accordingly.
package com.secret;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
/**
*
* #author secret
*/
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JacksonContextResolver implements ContextResolver<ObjectMapper> {
private ObjectMapper objectMapper;
public JacksonContextResolver() throws Exception {
this.objectMapper = new ObjectMapper();
this.objectMapper
.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
}
#Override
public ObjectMapper getContext(Class<?> objectType) {
return objectMapper;
}
}
Bear in mind that formatting has a negative effect on performance.
If you are using Spring, then you can globally set the property
spring.jackson.serialization.INDENT_OUTPUT=true
More info at https://docs.spring.io/spring-boot/docs/current/reference/html/howto-properties-and-configuration.html
Building on helpful DaTroop's answer, here is another version which allows choosing between optimized json and formatted json based on the absence or presence of a "pretty" parameter :
package test;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JacksonContextResolver implements ContextResolver<ObjectMapper> {
private ObjectMapper prettyPrintObjectMapper;
private UriInfo uriInfoContext;
public JacksonContextResolver(#Context UriInfo uriInfoContext) throws Exception {
this.uriInfoContext = uriInfoContext;
this.prettyPrintObjectMapper = new ObjectMapper();
this.prettyPrintObjectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
}
#Override
public ObjectMapper getContext(Class<?> objectType) {
try {
MultivaluedMap<String, String> queryParameters = uriInfoContext.getQueryParameters();
if(queryParameters.containsKey("pretty")) {
return prettyPrintObjectMapper;
}
} catch(Exception e) {
// protect from invalid access to uriInfoContext.getQueryParameters()
}
return null; // use default mapper
}
}
If you are using the jersey-media-json-binding dependency, which uses Yasson (the official RI of JSR-367) and JAVAX-JSON, you can introduce pretty printing as follows:
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbConfig;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
#Provider
public class RandomConfig implements ContextResolver<Jsonb> {
private final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withFormatting(true));
public RandomConfig() { }
#Override
public Jsonb getContext(Class<?> objectType) {
return jsonb;
}
}
Alternative for Jersey 1.x:
org.codehaus.jackson.map.ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationConfig.Feature.INDENT_OUTPUT);