Customising JSON output from JAX-RS services

528 views
Skip to first unread message

Riaz Tai

unread,
Feb 26, 2015, 6:48:56 AM2/26/15
to hippo-c...@googlegroups.com
Hi,

I've managed to configure the REST endpoint and able to return JSON responses for both plain and content aware JAX-RS services.

I'd like to know how to customise the JSON output. I've tried adding Jackson annotations (@JsonRootName and @JsonProperty) to the Representation classes but they seem to have no effect.

Any suggestions will be very much appreciated.

Thanks


marijan milicevic

unread,
Feb 26, 2015, 7:03:40 AM2/26/15
to hippo-c...@googlegroups.com
Hi,

have a look at  HST default provided config in:  org/hippoecm/hst/site/optional/jaxrs/SpringComponentManager-rest-jackson.xml 
(hst-jaxrs.xxx. jar). 
As an example of modifications have a look in [1]

cheers,
marijan




Thanks


--
Hippo Community Group: The place for all discussions and announcements about Hippo CMS (and HST, repository etc. etc.)
 
To post to this group, send email to hippo-c...@googlegroups.com
RSS: https://groups.google.com/group/hippo-community/feed/rss_v2_0_msgs.xml?num=50
---
You received this message because you are subscribed to the Google Groups "Hippo Community" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hippo-communi...@googlegroups.com.
Visit this group at http://groups.google.com/group/hippo-community.
For more options, visit https://groups.google.com/d/optout.

andi anderson

unread,
Feb 26, 2015, 7:11:17 AM2/26/15
to hippo-c...@googlegroups.com

Here is how we do it with faster jaxon

@Controller(value = "globalTemplateController")

@Path("/v1/globalTemplate")

@Produces({ MimeTypes.JSON })

public class GlobalTemplatesControllerV1Implextends AbstractRestController implementsGlobalTemplatesController {

private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(GlobalTemplatesControllerV1Impl.class);

@Autowired

private GlobalTemplateServiceglobalTemplateService;

private static String generateJSON(finalGlobalNavDocument globalNavDocument) {

final JsonFactory factory = new JsonFactory();

try(final StringWriter stringWriter = new StringWriter()) {

try (final JsonGenerator generator =factory.createGenerator(stringWriter)) {

generator.writeStartObject();

generator.writeArrayFieldStart("content");

generator.writeRaw(globalNavDocument.toJSON());

generator.writeEndArray();

generator.writeEndObject();

} catch (final IOException e) {

LOG.error(JSONHelper.CAUGHT_IO_EXCEPTION, e);

return StringUtils.EMPTY;

}

return stringWriter.toString();

} catch (final IOException e) {

LOG.error(JSONHelper.CAUGHT_IO_EXCEPTION, e);

return StringUtils.EMPTY;

}

}

@Override

@GET

@Path("/")

public @ResponseBody String getGlobalTemplate() {

final HstRequestContext hstContext = RequestContextProvider.get();

final GlobalNavDocument result =this.globalTemplateService.getGlobalTemplate(hstContext);

assert result != null : "did not get a result";

return generateJSON(result);

}

Riaz Tai

unread,
Feb 26, 2015, 9:08:38 AM2/26/15
to hippo-c...@googlegroups.com
Hi Andi,

Is your controller called by the JaxrsRestPlainPipeline?

andi anderson

unread,
Feb 26, 2015, 9:11:13 AM2/26/15
to hippo-c...@googlegroups.com
yes it is.

Woonsan Ko

unread,
Feb 26, 2015, 9:16:16 AM2/26/15
to hippo-c...@googlegroups.com
Hi Riaz,

IIRC, it is configured to scan JAXB annotations of your POJO
(representation) beans in (un)marshaling.
So, the ProductRepresentation example [1] uses @XmlRootElement(name =
"product") to customize the name (which can be picked up if the bean is
included in other document).
You can also use @XmlElement like the following to change the name from
"price" (by default property name taken by Jackson) to "unitPrice".

@XmlElement(name="unitPrice")
public double getPrice() {
return price;
}

Or, you can use @XmlAttribute as well:

@XmlAttribute(name="unitPrice")
public double getPrice() {
return price;
}

IIRC, it would serialize with "@unitPrice" : 9.99, instead of
"unitPrice": 9.99 though. Please try it out. :-)

Also you will find how to customize the collections there too:
@XmlElementWrapper(name="tags")
@XmlElements(@XmlElement(name="tag"))


Regards,

Woonsan

[1]
https://svn.onehippo.org/repos/hippo/hippo-cms7/testsuite/branches/hippo-testsuite-1.06.xx/components/src/main/java/org/hippoecm/hst/demo/jaxrs/model/ProductRepresentation.java
> --
> Hippo Community Group: The place for all discussions and announcements
> about Hippo CMS (and HST, repository etc. etc.)
>
> To post to this group, send email to hippo-c...@googlegroups.com
> RSS:
> https://groups.google.com/group/hippo-community/feed/rss_v2_0_msgs.xml?num=50
> ---
> You received this message because you are subscribed to the Google
> Groups "Hippo Community" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to hippo-communi...@googlegroups.com
> <mailto:hippo-communi...@googlegroups.com>.
> Visit this group at http://groups.google.com/group/hippo-community.
> For more options, visit https://groups.google.com/d/optout.


--
w....@onehippo.com www.onehippo.com
Boston - 745 Atlantic Ave, 8th Floor, Boston MA 02111
Amsterdam - Oosteinde 11, 1017 WT Amsterdam
US +1 877 414 4776 (toll free)
Europe +31(0)20 522 4466

Riaz Tai

unread,
Feb 26, 2015, 9:24:27 AM2/26/15
to hippo-c...@googlegroups.com
Hi Woonsan,

I've seen the ProductRepresentation and used to specify the root name and element names within my representation.
This works fine in XML but in JSON, the root name doesn't appear. I've tried adding @JsonRootName but it doesn't seem to work.

For example.

@XmlRootElement(name = "login")
@JsonRootName("login")
public class LoginRepresentation {

@XmlElement(name = "username-label")
@JsonProperty("username-label")
private String usernameLabel;

@XmlElement(name = "password-label")
@JsonProperty("password-label")
private String passwordLabel;

Woonsan Ko

unread,
Feb 26, 2015, 9:31:18 AM2/26/15
to hippo-c...@googlegroups.com
Hi Riaz,

By default, Jackson doesn't serialize the root object with a name.
So for example, if the root object of the return of your rest service is
LoginRepresentation, then it will serialize like this (BTW, I don't
think you need to have @JsonRootName and @JsonProperty there):

{
"username-label" : "Please enter your name",
"password-label" : "Please enter your password"
}

Now, if you want something like this instead:

{
"login" : {
"username-label" : "Please enter your name",
"password-label" : "Please enter your password"
}
}

then you can wrap the LoginRepresentation in something else.
For example,

@XmlRootElement(name = "loginResponse")
public class LoginResponse {

@XmlElement(name = "login")
private String LoginRepresentation loginRepresentation;

// ...
}

And, return LoginResponse wrapping LoginRepresentation instead. :-)

Regards,

Woonsan
> <javascript:>
> > RSS:
> >
> https://groups.google.com/group/hippo-community/feed/rss_v2_0_msgs.xml?num=50
> <https://groups.google.com/group/hippo-community/feed/rss_v2_0_msgs.xml?num=50>
>
> > ---
> > You received this message because you are subscribed to the Google
> > Groups "Hippo Community" group.
> > To unsubscribe from this group and stop receiving emails from it,
> send
> > an email to hippo-communi...@googlegroups.com <javascript:>
> > <mailto:hippo-communi...@googlegroups.com <javascript:>>.
> <http://groups.google.com/group/hippo-community>.
> > For more options, visit https://groups.google.com/d/optout
> <https://groups.google.com/d/optout>.
>
>
> --
> w....@onehippo.com <javascript:> www.onehippo.com
> <http://www.onehippo.com>
> Boston - 745 Atlantic Ave, 8th Floor, Boston MA 02111
> Amsterdam - Oosteinde 11, 1017 WT Amsterdam
> US +1 877 414 4776 (toll free)
> Europe +31(0)20 522 4466
>
> --
> Hippo Community Group: The place for all discussions and announcements
> about Hippo CMS (and HST, repository etc. etc.)
>
> To post to this group, send email to hippo-c...@googlegroups.com
> RSS:
> https://groups.google.com/group/hippo-community/feed/rss_v2_0_msgs.xml?num=50
> ---
> You received this message because you are subscribed to the Google
> Groups "Hippo Community" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to hippo-communi...@googlegroups.com
> <mailto:hippo-communi...@googlegroups.com>.

Riaz Tai

unread,
Feb 26, 2015, 10:02:16 AM2/26/15
to hippo-c...@googlegroups.com
Hi Woonsan,

From [1] it seems that we need to configure WRAP_ROOT_VALUE that would use the @XmlRootElement to wrap the json output.
How can this be configured in Hippo?

Thanks

>     > <mailto:hippo-community+unsub...@googlegroups.com <javascript:>>.
>     > Visit this group at http://groups.google.com/group/hippo-community
>     <http://groups.google.com/group/hippo-community>.
>     > For more options, visit https://groups.google.com/d/optout
>     <https://groups.google.com/d/optout>.
>
>
>     --
>     w....@onehippo.com <javascript:>     www.onehippo.com
>     <http://www.onehippo.com>
>     Boston - 745 Atlantic Ave, 8th Floor, Boston MA 02111
>     Amsterdam - Oosteinde 11, 1017 WT Amsterdam
>     US +1 877 414 4776 (toll free)
>     Europe +31(0)20 522 4466
>
> --
> Hippo Community Group: The place for all discussions and announcements
> about Hippo CMS (and HST, repository etc. etc.)
>  
> To post to this group, send email to hippo-c...@googlegroups.com
> RSS:
> https://groups.google.com/group/hippo-community/feed/rss_v2_0_msgs.xml?num=50
> ---
> You received this message because you are subscribed to the Google
> Groups "Hippo Community" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to hippo-communi...@googlegroups.com

Woonsan Ko

unread,
Feb 26, 2015, 10:26:31 AM2/26/15
to hippo-c...@googlegroups.com
Hi Riaz,

I think you can add a custom spring bean to HST container to achieve that.

For example, you can create a class like this:

package com.example;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

public class MyJacksonObjectMapperSerailizationFeaturesSetter {

private ObjectMapper objectMapper;
private boolean wrapRootValue;

public void setObjectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}

public void setWrapRootValue(boolean wrapRootValue) {
this.wrapRootValue = wrapRootValue;
}

public void init() {
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE,
wrapRootValue);
}
}

And, then add a .xml file under
site/src/main/resources/META-INF/hst-assembly/overrides/ (e.g,
'my-custom-jackson-settings.xml') with the following:

<beans ...>
<bean
class="com.example.MyJacksonObjectMapperSerailizationFeaturesSetter"
init-method="init">
<property name="objectMapper" ref="jaxrsRestJacksonObjectMapper" />
<property name="wrapRootValue" value="true" />
</bean>
</beans>

FYI, the "jaxrsRestJacksonObjectMapper" bean is already provided by HST
JAX-RS module, so you will be able to get access to it.
And, in your #init() method, you can probably invoke
ObjectMapper#configure() method to set the feature.

Let us know how it works.

HTH,

Woonsan
> > > <mailto:hippo-communi...@googlegroups.com
> <javascript:> <javascript:>>.
> > > Visit this group at
> http://groups.google.com/group/hippo-community
> <http://groups.google.com/group/hippo-community>
> > <http://groups.google.com/group/hippo-community
> <http://groups.google.com/group/hippo-community>>.
> > > For more options, visit https://groups.google.com/d/optout
> <https://groups.google.com/d/optout>
> > <https://groups.google.com/d/optout
> <https://groups.google.com/d/optout>>.
> >
> >
> > --
> > w....@onehippo.com <javascript:> www.onehippo.com
> <http://www.onehippo.com>
> > <http://www.onehippo.com>
> > Boston - 745 Atlantic Ave, 8th Floor, Boston MA 02111
> > Amsterdam - Oosteinde 11, 1017 WT Amsterdam
> > US +1 877 414 4776 (toll free)
> > Europe +31(0)20 522 4466
> >
> > --
> > Hippo Community Group: The place for all discussions and
> announcements
> > about Hippo CMS (and HST, repository etc. etc.)
> >
> > To post to this group, send email to hippo-c...@googlegroups.com
> <javascript:>
> > RSS:
> >
> https://groups.google.com/group/hippo-community/feed/rss_v2_0_msgs.xml?num=50
> <https://groups.google.com/group/hippo-community/feed/rss_v2_0_msgs.xml?num=50>
>
> > ---
> > You received this message because you are subscribed to the Google
> > Groups "Hippo Community" group.
> > To unsubscribe from this group and stop receiving emails from it,
> send
> > an email to hippo-communi...@googlegroups.com <javascript:>
> > <mailto:hippo-communi...@googlegroups.com <javascript:>>.
> > Visit this group at http://groups.google.com/group/hippo-community
> <http://groups.google.com/group/hippo-community>.
> > For more options, visit https://groups.google.com/d/optout
> <https://groups.google.com/d/optout>.
>
>
> --
> w....@onehippo.com <javascript:> www.onehippo.com
> <http://www.onehippo.com>
> Boston - 745 Atlantic Ave, 8th Floor, Boston MA 02111
> Amsterdam - Oosteinde 11, 1017 WT Amsterdam
> US +1 877 414 4776 (toll free)
> Europe +31(0)20 522 4466
>
> --
> Hippo Community Group: The place for all discussions and announcements
> about Hippo CMS (and HST, repository etc. etc.)
>
> To post to this group, send email to hippo-c...@googlegroups.com
> RSS:
> https://groups.google.com/group/hippo-community/feed/rss_v2_0_msgs.xml?num=50
> ---
> You received this message because you are subscribed to the Google
> Groups "Hippo Community" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to hippo-communi...@googlegroups.com
> <mailto:hippo-communi...@googlegroups.com>.

Riaz Tai

unread,
Feb 26, 2015, 6:49:58 PM2/26/15
to hippo-c...@googlegroups.com
Hi,

The problem I'm getting with this is

java.lang.IllegalStateException: Cannot convert value of type [org.codehaus.jackson.map.ObjectMapper] to required type [com.fasterxml.jackson.databind.ObjectMapper] for property 'objectMapper':

It seems HST is using an old version of Jackson. 

Woonsan Ko

unread,
Feb 26, 2015, 8:54:11 PM2/26/15
to hippo-c...@googlegroups.com
Hi Riaz,

Ah, sorry, I looked up the trunk source. I should have looked up 2.28.xx
branch source (which is for 7.9.x) instead.
In 7.9, it is using Jackson 1.9.x and the objectMapper is defined as
follows [1]:

<bean id="jaxrsRestJacksonObjectMapper"
class="org.codehaus.jackson.map.ObjectMapper">
</bean>

Therefore, my example should like the following instead:

package com.example;

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;

public class MyJacksonObjectMapperSerailizationFeaturesSetter {

private ObjectMapper objectMapper;
private boolean wrapRootValue;

public void setObjectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}

public void setWrapRootValue(boolean wrapRootValue) {
this.wrapRootValue = wrapRootValue;
}

public void init() {
objectMapper.configure(
SerializationConfig.Feature.WRAP_ROOT_VALUE,
wrapRootValue);
}
}

And, then add a .xml file under
site/src/main/resources/META-INF/hst-assembly/overrides/ (e.g,
'my-custom-jackson-settings.xml') with the following:

<beans ...>
<bean
class="com.example.MyJacksonObjectMapperSerailizationFeaturesSetter"
init-method="init">
<property name="objectMapper" ref="jaxrsRestJacksonObjectMapper" />
<property name="wrapRootValue" value="true" />
</bean>
</beans>

FYI, the "jaxrsRestJacksonObjectMapper" bean is provided by HST
JAX-RS module [1], so you will be able to get access to it.
And, in your #init() method, you can probably invoke
ObjectMapper#configure() method to set the feature.

HTH,

Woonsan

[1]
http://svn.onehippo.org/repos/hippo/hippo-cms7/site-toolkit/branches/hst-2.28.xx/components/jaxrs/src/main/resources/org/hippoecm/hst/site/optional/jaxrs/SpringComponentManager-rest-jackson.xml

Riaz Tai

unread,
Mar 17, 2015, 11:35:38 AM3/17/15
to hippo-c...@googlegroups.com
Thanks Woonsan,

That worked perfectly.

I have now updated my representation to the following:

@XmlRootElement(name = "login")
@JsonRootName("login")
public class LoginRepresentation {

    @XmlElement(name = "username-label")
    @JsonProperty("username-label")
    private String usernameLabel;

    @XmlElement(name = "password-label")
    @JsonProperty("password-label")
    private String passwordLabel;

    private List<Link> links;

    @XmlElement(name = "images")
    private List<ImageLinkRepresentation> images;

It all works fine except the JSON response contains an array of images where each element looks like this:

        "images": [
            {
                "ImageLinkRepresentation": {
                    "alt": "login-card-mobile.png"
                }
            },

My ImageLinkRepresentation class looks like this:

@XmlRootElement(name = "img")
@JsonRootName("img")
public class ImageLinkRepresentation {

    @XmlAttribute(name = "src")
    private String src;

    @XmlAttribute(name = "alt")
    private String alt;

So I'm at a loss as to why the JSON response contains the class name.

Thanks again for your help.

Regards
Riaz

Woonsan Ko

unread,
Mar 17, 2015, 1:36:34 PM3/17/15
to hippo-c...@googlegroups.com
Could you try with this?

@XmlElementWrapper(name = "images")
private List<ImageLinkRepresentation> images;

Regards,

Woonsan
> <javascript:>
> > RSS:
> >
> https://groups.google.com/group/hippo-community/feed/rss_v2_0_msgs.xml?num=50
> <https://groups.google.com/group/hippo-community/feed/rss_v2_0_msgs.xml?num=50>
>
> > ---
> > You received this message because you are subscribed to the Google
> > Groups "Hippo Community" group.
> > To unsubscribe from this group and stop receiving emails from it,
> send
> > an email to hippo-communi...@googlegroups.com <javascript:>
> > <mailto:hippo-communi...@googlegroups.com <javascript:>>.
> <http://groups.google.com/group/hippo-community>.
> > For more options, visit https://groups.google.com/d/optout
> Boston - 745 Atlantic Ave, 8th Floor, Boston MA 02111
> Amsterdam - Oosteinde 11, 1017 WT Amsterdam
> US +1 877 414 4776 (toll free)
> Europe +31(0)20 522 4466
>
> --
> Hippo Community Group: The place for all discussions and announcements
> about Hippo CMS (and HST, repository etc. etc.)
>
> To post to this group, send email to hippo-c...@googlegroups.com
> RSS:
> https://groups.google.com/group/hippo-community/feed/rss_v2_0_msgs.xml?num=50
> ---
> You received this message because you are subscribed to the Google
> Groups "Hippo Community" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to hippo-communi...@googlegroups.com
> <mailto:hippo-communi...@googlegroups.com>.
Reply all
Reply to author
Forward
0 new messages