I am currently working on a RESTful API. I have an Employee class and an EmployeeResource class. I also have a custom DateAdapter, which changes my Date properties to Long timestamps. However, my JSON responses are showing the timestamps as strings (wrapped in double quotes) rather than numbers (without double quotes). Here is an abbreviated version of my code and captured JSON response...
Custom DateAdapter
public class DateAdapter extends XmlAdapter<Long, Date> {
@Override
public Date unmarshal(Long v) throws Exception {
return new Date(Long.valueOf(v));
}
@Override
public Long marshal(Date v) throws Exception {
return v.getTime();
}
}
Entity Class
@Entity
@javax.xml.bind.annotation.XmlRootElement
@XmlType(propOrder={"createdOn","empId"})
public class Employee implements Serializable {
private Date createdOn;
private Integer empId;
@Column(nullable=false)
@Temporal(TemporalType.TIMESTAMP)
@XmlJavaTypeAdapter(DateAdapter.class)
public Date getCreatedOn() {
return createdOn;
}
public void setCreatedOn(Date createdOn) {
this.createdOn = createdOn;
}
@Id
@XmlID
public Integer getEmpId() {
return empId;
}
public void setEmpId(Integer empId) {
this.empId = empId;
}
}
EmployeeResource
@Path("/Employees")
@javax.xml.bind.annotation.XmlRootElement
@XmlType(propOrder={"hateoas","employees"})
public class EmployeeResource {
List<Employee> employees;
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
@GET
@Path("/{id}")
@Produces("application/json")
public Response getEmployee(@Context UriInfo ui, @PathParam("id") Integer id) {
Session session = HibernateUtil.getSession();
session.beginTransaction();
Criteria criteria=session.createCriteria(Employee.class);
criteria.add(Restrictions.eq("empId", new Integer(10150)));
this.employees = criteria.list();
return Response.ok(this).build();
}
}
Current JSON response
{
"employees":{
"createdOn":"1330915130163",
"empId":"10150"
}
}
Expected JSON response
{
"employees":{
"createdOn":1330915130163,
"empId":10150
}
}
I'm assuming that there's some way to prevent JAXB or JAX-RS from wrapping all numbers in quotes. Could someone guide me to where I could configure this?
Thanks in advance!
EDIT #1 2012.03.07 Ok, so after some more researching, I think my problem is with the default JSONConfiguration.Notation being used, MAPPED . It looks like the NATURAL JSONConfiguration.Notation would get me what I want. However, I haven't found a clear example of how to apply that application wide. I'm assuming I would specify this in my ApplicationConfig class that extends javax.ws.rs.core.Application .
EDIT #2 2012.03.10 Ok, so after some more researching I decided to use the JSON parser library, Jackson. It seems to be the most complete JSON solution out there using just the default configuration. By default Dates are translated to their corresponding timestamps and serialized as numbers (w/o quotes). The only drawback I have come across is that Jackson currently does not support the JAXB annotations, "@XmlID" and "@XmlIDREF". Since I have direct self-references in my data model (not shown above), I have created another question to discuss. If anyone is interested click here to follow that thread...
Java Architecture for XML Binding (JAXB) is an XML-to-Java binding technology that simplifies the development of web services by enabling transformations between schema and Java objects and between XML instance documents and Java object instances.
Your JAX-RS application can use the JAXB objects to manipulate XML data.
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
The JAX-RS implementation you are using may be using a JAXB (JSR-222) implementation with something like Jettison to produce JSON. Jettison provides a StAX API to interact with JSON, since that StAX APIs don't have any sort of typing WRT text, all the simple values get treated as strings:
To get the behaviour you are looking for you can use a different binding solution. We are adding this support into the MOXy component for EclipseLink 2.4:
To configure MOXy as your JSON-binding provider in a JAX-RS environment, you could create a MessageBodyReader
/MessageBodyWriter
that looks like:
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Collection;
import javax.xml.transform.stream.StreamSource;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.ext.*;
import javax.xml.bind.*;
@Provider
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class MOXyJSONProvider implements
MessageBodyReader<Object>, MessageBodyWriter<Object>{
@Context
protected Providers providers;
public boolean isReadable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return true;
}
public Object readFrom(Class<Object> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
throws IOException, WebApplicationException {
try {
Unmarshaller u = getJAXBContext(type, mediaType).createUnmarshaller();
u.setProperty("eclipselink.media-type", mediaType.toString());
u.setProperty("eclipselink.json.include-root", false);//tiny fix
return u.unmarshal(new StreamSource(entityStream), (Class) genericType);
} catch(JAXBException jaxbException) {
throw new WebApplicationException(jaxbException);
}
}
public boolean isWriteable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return true;
}
public void writeTo(Object object, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) throws IOException,
WebApplicationException {
try {
Marshaller m = getJAXBContext(Customer.class, mediaType).createMarshaller();
m.setProperty("eclipselink.media-type", mediaType.toString());
m.setProperty("eclipselink.json.include-root", false);
m.marshal(object, entityStream);
} catch(JAXBException jaxbException) {
throw new WebApplicationException(jaxbException);
}
}
public long getSize(Object t, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return -1;
}
private JAXBContext getJAXBContext(Class<?> type, MediaType mediaType)
throws JAXBException {
ContextResolver<JAXBContext> resolver
= providers.getContextResolver(JAXBContext.class, mediaType);
JAXBContext jaxbContext;
if(null == resolver || null == (jaxbContext = resolver.getContext(type))) {
return JAXBContext.newInstance(type);
} else {
return jaxbContext;
}
}
}
For More Information
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With