MOXy JSON support - json

I'm using EclipseLink's MOXy as the JAXB implementation in my RESTEasy project.MOXy's advanced functionality which has been brought by annotations like #XmlDiscriminatorNode & Value helped me a lot. Everything's working fine except one thing: JSON support. I'm using JettisonMappedContext of RESTEasy but unfortunately there're only instance variable fields belong to the abstract superclass in my JSON after marshalling.
#XmlRootElement
#XmlDiscriminatorNode("#type")
public abstract class Entity {
public Entity(){}
public Entity(String id){
this.id = id;
}
private String id;
#XmlElement
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Subclass:
#XmlRootElement
#XmlDiscriminatorValue("photo")
public class Photo extends Entity{
private String thumbnail;
public Photo(){}
public Photo(String id) {
super(id);
}
public void setThumbnail(String thumbnail) {
this.thumbnail = thumbnail;
}
#XmlElement(name="thumbnail")
public String getThumbnail() {
return thumbnail;
}
}
XML after marshalling:
<object type="photo">
<id>photoId423423</id>
<thumbnail>http://dsadasadas.dsadas</thumbnail>
</object>
JSON after marshalling:
"object":{"id":"photoId423423"}
Is there any other way to achieve this?
Thank you.

UPDATE 2
EclipseLink 2.4 has been released with MOXy's JSON binding:
http://www.eclipse.org/eclipselink/releases/2.4.php
UPDATE 1
Get a sneak peak of the native MOXy object-to-JSON binding being added in EclipseLink 2.4:
http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html
Ensure that you have included a file named jaxb.properties file with your model classes that contains the following entry:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Without this entry the reference implementation will be used, and the EclipseLink JAXB (MOXy) extensions will not appear in the resulting XML/JSON.
Using the #DescrimatorNode example from my blog, the XML produced would be:
<customer>
<contactInfo classifier="address-classifier">
<street>1 A Street</street>
</contactInfo>
</customer>
When I marshal leveraging Jettison:
StringWriter strWriter = new StringWriter();
MappedNamespaceConvention con = new MappedNamespaceConvention();
AbstractXMLStreamWriter w = new MappedXMLStreamWriter(con, strWriter);
marshaller.marshal(customer, w);
System.out.println(strWriter.toString());
Then I get the following JSON:
{"customer":{"contactInfo":{"#classifier":"address-classifier","street":"1 A Street"}}}
For more information on JAXB and JSON see:
http://bdoughan.blogspot.com/2011/04/jaxb-and-json-via-jettison.html

Related

Custom Jackson Deserializers and Arrays

Please note: There are many questions on this site about how to use custom Jackson deserializers...this question is not one more of those! This questions has to do with using a deserializer under very unique circumstances (none of which have previous questions/answers on this site!).
Spring Boot using Jackson for JSON serialization here. I have two POJOs that are used in the #RequestBody (HTTP request entity) for a POST endpoint:
#JsonDeserialize(using = FizzDeserializer.class)
public class Fizz {
private String name;
private String label;
private Integer code;
// Getters, setters & ctors
}
#JsonDeserialize(using = BuzzDeserializer.class)
public class Buzz {
private String id;
private String locale;
private Set<Fizz> fizzes;
// Getters, setters & ctors
}
#RestController
#RequestMapping("v1/data/buzzes")
public class BuzzController {
#PostMapping
public void updateBuzz(#RequestBody Buzz buzz) {
// do whatever
}
}
I want HTTP clients to be able to POST the following JSON to this endpoint:
{
"id" : "12345-67890",
"locale" : "en_US",
"fizzes" : [
"foo",
"bar"
]
}
...where "foo" and "bar" are the Fizz#names of two different Fizz instances. In other words, I don't want the client to have to specify the entire Fizz object, just specify its name as a JSON string (my app + DB guarantee Fizzes have unique names).
So I'm using a custom JsonDeserializer to accomplish all this mapping:
public BuzzDeserializer extends JsonDeserializer<Buzz> {
Buzz deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode buzzNode = jsonParser.readValueAsTree();
String id = buzzNode.get("id");
String locale = buzzNode.get("locale");
// TODO: How to read "foo" and "bar" (etc.) into a Set<Fizz> instances?
Set<Fizz> fizzes = ???
new Buzz(id, locale, fizzes);
}
}
public FizzDeserializer extends JsonDeserializer<Fizz> {
private FizzDAO fizzDAO;
// Getters, setters & ctors...
Fizz deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode fizzNode = jsonParser.readValueAsTree();
// If I can get access to "foo"/"bar"/etc. string somehow, I can look up the Fizz using the DAO:
String fooBarEtcStr = ???
return fizzDAO.findFizzByName(fooBarEtcStr);
}
However I'm not sure how I can read the JSON fizzes array into a Set<Fizz> inside this deserializer. Any ideas?
Taken from the comments to the question, the only problem seems to be to get the JSON array. Provided that the service or DAO to lookup is already injected or provided in the serializer, try something along the lines:
final JsonNode arr = buzzNode.get("fizzes");
if (arr.isArray()) {
final Set<Fizz> fizzes = Sets.newHashSetWithExpectedSize(arr.size());
for (JsonNode obj : arr) {
final String name = obj.asText();
Fizz fizz = // load from DAO
fizzes.add(fizz);
}
}
This can of course be optimised by collecting the String values and use only one DAO call. Also some java8 streaming could make the code less verbose.

Jersey Jackson unmarshall JSON

I am working on an embedded jersey instance which will run a JAXB RESTful service. I have configured Jackson with two steps:
Adding this to my POM
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.23.2</version>
</dependency>
Registering it in my application
public HandheldApplication() {
scripts.add(HandheldServer.class);
scripts.add(BasicScript.class);
// Add JacksonFeature.
scripts.add(JacksonFeature.class);
scripts.add(LoggingFilter.class);
}
I have a complex object being passed back and forth as shown below:
package com.ziath.handheldserver.valueobjects;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.*;
#SuppressWarnings("restriction")
#XmlRootElement
public class Widget {
private String key;
private String name;
private List<String> options = new ArrayList<String>();
private String value;
private String type;
public Widget(){
super();
}
public Widget(String key, String name, List<String> options, String value,
String type) {
super();
this.key = key;
this.name = name;
this.options = options;
this.value = value;
this.type = type;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getOptions() {
return options;
}
public void setOptions(List<String> options) {
this.options = options;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
When I execute this in a GET method as shown below:
#Override
#GET
#Path("getKeys")
#Produces(MediaType.APPLICATION_JSON)
public List<Widget> getKeys(#QueryParam(value = "page") int page)
This works fine and I get JSON back; however when I execute it is a PUT as shown below:
#Override
#PUT
#Path("validateKeys")
#Produces({MediaType.APPLICATION_JSON})
#Consumes(MediaType.APPLICATION_JSON)
public boolean validateKeys(#QueryParam(value = "page")int page, #QueryParam(value = "widgets")List<Widget> widgets)
When I execute a PUT to access this method I get a stack trace as follows:
Caused by: org.glassfish.jersey.internal.inject.ExtractorException: Error un-marshalling JAXB object of type: class com.ziath.handheldserver.valueobjects.Widget.
at org.glassfish.jersey.jaxb.internal.JaxbStringReaderProvider$RootElementProvider$1.fromString(JaxbStringReaderProvider.java:195)
at org.glassfish.jersey.server.internal.inject.AbstractParamValueExtractor.convert(AbstractParamValueExtractor.java:139)
at org.glassfish.jersey.server.internal.inject.AbstractParamValueExtractor.fromString(AbstractParamValueExtractor.java:130)
at org.glassfish.jersey.server.internal.inject.CollectionExtractor.extract(CollectionExtractor.java:88)
at org.glassfish.jersey.server.internal.inject.CollectionExtractor$ListValueOf.extract(CollectionExtractor.java:107)
at org.glassfish.jersey.server.internal.inject.QueryParamValueFactoryProvider$QueryParamValueFactory.provide(QueryParamValueFactoryProvider.java:89)
... 38 more
Caused by: javax.xml.bind.UnmarshalException
- with linked exception:
[org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.]
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.createUnmarshalException(AbstractUnmarshallerImpl.java:335)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.createUnmarshalException(UnmarshallerImpl.java:563)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:249)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:214)
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:140)
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:123)
at org.glassfish.jersey.jaxb.internal.JaxbStringReaderProvider$RootElementProvider$1.fromString(JaxbStringReaderProvider.java:190)
... 43 more
So it seems to me that Jackson is correctly marshalling my POJO into JSON but trying to unmarshall it as XML. Note that I switched to Jackson away from MOXy because I needed to be able to handle collections coming back and forth and apparently MOXy cannot do that.
Is there a setting I've missed to tell Jackson/Jersey to go both ways for JSON?
Try removing #QueryParam(value = "widgets") because you should pass it as entity body - not query param.
#PUT
#Path("validateKeys")
#Produces({MediaType.APPLICATION_JSON})
#Consumes(MediaType.APPLICATION_JSON)
public boolean validateKeys(#QueryParam(value = "page")int page, List<Widget> widgets)
Also you can make wrapper class:
#XmlRootElement
public class Widgets {
private List<Widget> widgets;
// other fields, setters and getters
}
And then:
#PUT
#Path("validateKeys")
#Produces({MediaType.APPLICATION_JSON})
#Consumes(MediaType.APPLICATION_JSON)
public boolean validateKeys(#QueryParam(value = "page")int page, Widgets widgets)
I would suggest to read some discussions about REST design because you're using verbs in your paths:
Is this a bad REST URL?
Understanding REST: Verbs, error codes, and authentication
I was switching between QueryParam and FormParam to try and get one of them to work. If I use FormParam I also need to change the consumes to APPLICATION_FORM_URLENCODED.
The actual issue was that the default unmarshalling with Jackson was using XML because it was tagged as an XML resource - take that out! I finally managed to work out how to unmarshall from JSON by using a static fromString method. Then to handle the list; I cannot use a wrapper class because this needs to be highly cross language and exposing a wrapper with a list would have complicated the implementation from Python, C#, etc. The way to get it to accept a list with a wrapper is to post the name of the param (in this case widgets) multiple time. Then each JSON passed in will be called against the fromString method.

How to send/accept JSON using JerseyTest Framework

I am attempting to write a simple test class that emulates a RESTful Web Service creating a Customer via a POST method. The following fails at assertEquals, I receive a 400 Bad Request response. I cannot use debugger to observe stack trace. However the console tells me the following...
INFO: Started listener bound to [localhost:9998]
INFO: [HttpServer] Started.
public class SimpleTest extends JerseyTestNg.ContainerPerMethodTest {
public class Customer {
public Customer() {}
public Customer(String name, int id) {
this.name = name;
this.id = id;
}
#JsonProperty("name")
private String name;
#JsonProperty("id")
private int id;
}
#Override
protected Application configure() {
return new ResourceConfig(MyService.class);
}
#Path("hello")
public static class MyService {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public final Response createCustomer(Customer customer) {
System.out.println("Customer data: " + customer.toString());
return Response.ok("customer created").build();
}
}
#Test
private void test() {
String json = "{" +
"\"name\": \"bill\", " +
"\"id\": 4" +
"}";
final Response response = target("hello").request(MediaType.APPLICATION_JSON_TYPE).post(Entity.json(json));
System.out.println(response.toString());
assertEquals(response.getStatus(), 200);
}
}
Instead of printing response.toString(), you can read the actual body using response.readEntity(String.class). What you will find in the body is an error message from Jackson
No suitable constructor found for type [simple type, class simple.SimpleTest$Customer]: can not instantiate from JSON object (need to add/enable type information?)
At first glance your Customer class looks ok; it has a default constructor. But the really problem is that Jackson cannot instantiate it because it is a non-static inner class. So to fix it, simply make the Customer class static.
public static class Customer {}
As a general rule, when working with JSON and Jackson with Jersey, often when you get a 400, it a a problem with Jackson, and Jackson is pretty good at spitting out a meaningful message that will help us debug.

How to marshall a Java class to JSON with JAXB, JAX-RS 2.0 and MOXy

I have a web application Running on Tomcat 7 that used Jersey 1.8 to provide a REST service producing a JSON file based on the class file shown below.
#XmlRootElement
public class Person {
public String firstname;
public String lastname;
public String email;
public Person() {
}
public Person(String firstname, String lastname, String email) {
this.firstname = firstname;
this.lastname = lastname;
this.email = email;
}
}
I use the service shown below to produce a JSON file.
#GET
#Path("allpersons")
#Produces(MediaType.APPLICATION_JSON)
public Collection<Person> getAllPersons() {
Person p1 = new Person("Albert","Marks","albert.marks#email.com");
Person p2 = new Person("David","Spencer","david.spencer#email.com");
Collection<Person> all = new ArrayList<>();
all.add(p1);
all.add(p2);
return all;
}
This would lead to a JSON file with the following form:
{ "person": [
{ "firstname":"Albert",
"lastname":"Marks",
"email":"albert.marks#email.com"},
{ "firstname":"David",
"lastname":"Spencer",
"david.spencer#email.com"}
]
}
This all worked just fine but then I had to update to Jersey 2.10. Since them I also use jersey-media-moxy 2.10. However, the resulting JSON now is as follows which breaks JavaScript libraries calling the service:
[
{ "firstname":"Albert",
"lastname":"Marks",
"email":"albert.marks#email.com"},
{ "firstname":"David",
"lastname":"Spencer",
"david.spencer#email.com"}
]
I compared the XML files produced using both versions of Jersey and they are absolutely identical. Hence I guess it must be some configuration issue with MOXy. I tried providing an own class inheriting javax.ws.rs.core.Application but the effect is still the same. Any idea what the cause is?
MOXy's JSON-binding renders a Java java.util.Collection as a JSON array (make's sense right?). If you want it to include the root level person key then instead of returning Collection<Person> you could return an instance of a class that had a Collection property called person or annotated with #XmlElement(name="person").

Process JSON with Jersey and Jackson

I am using jersey in Java. I want to get JSON data sent via a post request. However, I am not sure how to do this, despite my searching. I am able to receive JSON data at a path, yet I can't figure out how to parse it into java variables. I assume that I need to use jackson to do this. However, I don't understand how to pass the received JSON to jackson.
#Path("/register")
public class ResourceRegister
{
#POST
#Consumes(MediaType.APPLICATION_JSON)
public String RegisterUser(//not sure what to take in here to get the json )
{
//code to deal with the json
}
There are several ways of accepting the JSON and using it in back-end.
1. set POJO elements using JAXB APIs and use object of that POJO class to access passed parameters. this will be helpful while JSON size is large.
Example:
your service declaration would be as following
#Path("/register")
public class ResourceRegister
{
#POST
#Consumes(MediaType.APPLICATION_JSON)
public String RegisterUser(RegParams regParams)
{
//code to deal with the json
}
.....
}
and you will write a POJO like following
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
#JsonIgnoreProperties(ignoreUnknown=true)
#JsonWriteNullProperties(false)
public class RegParams implements Serializable {
#JsonProperty("userId")
private long userId;
#JsonProperty("userName")
private String userName;
..
..
}
retrive JSON as a string and use jersey APIs to work with the same.
in this case you can declare your service as following
#Path("/register")
public class ResourceRegister
{
#POST
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public String RegisterUser(#FormParam("jsonObj")String jsonString)
{
//code to deal with the json
}
.....
}
and you can process that string by using jersey APIs like following
ObjectMapper om = new ObjectMapper();
JsonNode mainNode = om.readTree(jsonString);
//access fields
mainNode.get..(as per data passed, string, int etc)
for more referance you can refer this or this
You just need to place #JsonProperty annotation to your class properties and add that class to your Resource method as paramater.
You might need #JsonIgnoreProperties annotation as well if you are not deserializing everything inside the incoming json
See below:
#POST
#Consumes(MediaType.APPLICATION_JSON)
public String registerUser(MyUser myUser)
{
//code to deal with the json
}
public class MyUser{
#JsonProperty
private String name;
#JsonProperty
private String surname;
//getters & setters & constructors if you need
}