JAXB 或 JAX-RS 在我的 JSON 响应中用引号包裹数字,将它们变成字符串.为什么这是默认行为,以及如何解决?

2022-01-19 00:00:00 java jax-rs jaxb

我目前正在开发一个 RESTful API.我有一个 Employee 类和一个 EmployeeResource 类.我还有一个自定义的 DateAdapter,它将我的 Date 属性更改为长时间戳.但是,我的 JSON 响应将时间戳显示为字符串(用双引号括起来)而不是数字(不带双引号).这是我的代码和捕获的 JSON 响应的缩写版本...

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...

自定义日期适配器

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
@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;
    }
}

员工资源

@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();
    }
}

当前 JSON 响应

{
  "employees":{
    "createdOn":"1330915130163",
    "empId":"10150"
  }
}

预期的 JSON 响应

Expected JSON response

{
  "employees":{
    "createdOn":1330915130163,
    "empId":10150
  }
}

我假设有一些方法可以防止 JAXB 或 JAX-RS 将所有数字包含在引号中.有人可以指导我在哪里进行配置吗?

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?

提前致谢!

编辑 #1 2012.03.07好的,所以经过更多研究,我认为我的问题在于使用的默认 JSONConfiguration.Notation MAPPED .看起来 NATURAL JSONConfiguration.Notation 会让我得到我想要的.但是,我还没有找到一个明确的例子来说明如何应用该应用程序.我假设我会在扩展 javax.ws.rs.core.Application 的 ApplicationConfig 类中指定它.

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 .

编辑 #2 2012.03.10好的,所以经过更多研究后,我决定使用 JSON 解析器库 Jackson.它似乎是仅使用默认配置的最完整的 JSON 解决方案.默认情况下,日期被转换为相应的时间戳并序列化为数字(不带引号).我遇到的唯一缺点是 Jackson 目前不支持 JAXB 注释@XmlID"和@XmlIDREF".由于我的数据模型中有直接的自引用(上面未显示),因此我创建了另一个问题来讨论.如果有人有兴趣 点击此处关注该主题...

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...

推荐答案

注意:我是EclipseLink JAXB (MOXy) 领导和 JAXB 2 (JSR-222) 专家组.

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.

您正在使用的 JAX-RS 实现可能正在使用 JAXB (JSR-222) 实现类似 Jettison 生成 JSON.Jettison 提供了一个 StAX API 来与 JSON 交互,因为 StAX API 没有任何类型的输入 WRT 文本,所有简单的值都被视为字符串:

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:

  • http://blog.bdoughan.com/2011/04/jaxb-and-json-via-jettison.html

要获得您正在寻找的行为,您可以使用不同的绑定解决方案.我们正在将此支持添加到 EclipseLink 2.4 的 MOXy 组件中:

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:

  • http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html

要将 MOXy 配置为 JAX-RS 环境中的 JSON 绑定提供程序,您可以创建一个 MessageBodyReader/MessageBodyWriter,如下所示:

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;
        }
    }

}

更多信息

  • https://stackoverflow.com/a/8989659/383861
  • http://blog.bdoughan.com/2010/08/creating-restful-web-service-part-15.html
  • http://blog.bdoughan.com/2011/04/moxys-xml-metadata-in-jax-rs-service.html

相关文章