如何将 Java 日历对象的 JSON 表示传递给 REST 服务?

How to pass JSON representations of Java Calendar objects to REST service?

这是我的问题的简化版本。考虑以下 REST 服务...

@Stateless
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Path("/test")
public class TestResource {


    @POST
    public Response test(Calendar c) {
        System.out.println(c);
        return Response.ok(Response.Status.OK).build();
    }

    @GET
    @Path("/getCalendar")
    public Response getCalendar() {
        return Response.ok(toJSONString(Calendar.getInstance())).build();
    }

    public String toJSONString(Object object) {
        GsonBuilder builder = new GsonBuilder();
        builder.setDateFormat("yyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        Gson gson = builder.create();
        return gson.toJson(object);
    }
}

当然,这...

@ApplicationPath("/rest")
public class RestActivator extends Application {

}

我正在使用 Postman,起初我通过向“http://localhost:8080/ProjectName/rest/test/getCalendar”发送 GET 请求获得日历对象的 JSON 表示。然后我复制返回的 JSON,即

{
  "year": 2015,
  "month": 5,
  "dayOfMonth": 29,
  "hourOfDay": 10,
  "minute": 7,
  "second": 24
}

然后我使用 Postman 将 POST 发送到“http://localhost:8080/ProjectName/rest/”,其中包含上面返回给我的数据。然后 'javax.ws.rs.NotSupportedException: Cannot consume content type' 被抛出。

我该如何解决这个问题,以便服务可以处理日历对象(以及 类 具有日历对象作为字段的对象)?

编辑:

是的,我正在设置正确的内容类型,即 application/json。

这是我收到的回复...

com.fasterxml.jackson.databind.JsonMappingException:无法从 START_OBJECT 令牌中反序列化 java.util.Calendar 的实例 在[来源:io.undertow.servlet.spec.ServletInputStreamImpl@4cf87599;行:1,列:1]

更新: 为了让它工作,我使用了@peeskillets 解决方案。

您需要在请求中传入正确的内容类型并检查您是否提供了正确的路径..

Jackson 通常只适用于 JavaBean 风格的 POJO,而 Calendar 则不行。对于这些情况,Jackson 允许我们创建自定义反序列化器。一旦我们创建了反序列化器,我们就可以在 ContextResolver 中用 ObjectMapper 注册它。例如

public class CalendarDeserializer extends JsonDeserializer<Calendar>  {

    @Override
    public Calendar deserialize(JsonParser jp, DeserializationContext dc) 
            throws IOException, JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        int year = getInt("year", node);
        int month = getInt("month", node);
        int dayOfMonth = getInt("dayOfMonth", node);
        int hourOfDay = getInt("hourOfDay", node);
        int minute = getInt("minute", node);
        int second = getInt("second", node);

        Calendar c = Calendar.getInstance();
        c.set(year, month, dayOfMonth, hourOfDay, minute, second);
        return c;
    }

    private int getInt(String name, JsonNode node) {
        return (Integer) ((IntNode) node.get(name)).numberValue();
    } 
}

要用 ObjectMapper 注册它,我们可以做

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Calendar.class, new CalendarDeserializer());
mapper.registerModule(module);

要将 ObjectMapper 与我们的 JAX-RS 应用程序一起使用,我们可以在 ContextResolver 中创建它,如 . You will need to add the jackson-databind 所示,如果您还没有它。

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>${jackson2.version}</version>
</dependency>

请注意,JAX-RS 的 Jackson JSON 提供程序已附带上述依赖项,因此如果您已经拥有 Jackson JSON 支持依赖项,则不需要它。

我已经用你提供的 JSON 测试了这个,它工作正常。

查看更多关于自定义反序列化器的信息here

所以在做了一些研究并感谢那些回答(特别是@peeskillet)的人之后,我想出了 2 个解决这个问题的方法。

解决方案 1

按照@peeskillet 的回答创建一个自定义反序列化器。那行得通,这是我添加的 2 类 以使其起作用...

import java.util.Calendar;

import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

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

@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {

    private final ObjectMapper mapper;

    public ObjectMapperContextResolver() {

        mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addDeserializer(Calendar.class, new CalendarDeserializer());
        mapper.registerModule(module);
        mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
   }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return mapper;
    }
}

以及自定义解串器

import java.io.IOException;
import java.util.Calendar;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.IntNode;

public class CalendarDeserializer extends JsonDeserializer<Calendar>  {

    private int getInt(String name, JsonNode node) {
        return (Integer) ((IntNode) node.get(name)).numberValue();
    }

    @Override
    public Calendar deserialize(JsonParser jp, com.fasterxml.jackson.databind.DeserializationContext dc) throws IOException,
            JsonProcessingException {
        JsonNode node = jp.getCodec().readTree(jp);
        int year = getInt("year", node);
        int month = getInt("month", node);
        int dayOfMonth = getInt("dayOfMonth", node);
        int hourOfDay = getInt("hourOfDay", node);
        int minute = getInt("minute", node);
        int second = getInt("second", node);

        Calendar c = Calendar.getInstance();
        c.set(year, month, dayOfMonth, hourOfDay, minute, second);
        return c;
    } 
}

解决方案 2

不过,我还想出了另一个可能更简单的解决方案。如果我在@GET 方法中省略了 GSON 到 JSONString(不知道为什么我混合使用 Jackson 和 GSON...)并简单地返回 Response.ok(Calendar.getInstance()).build();,Jackson 将 Calendar 对象转换为它的原始 long 值.当我将一个 long 值传递给 @POST 方法并将 Calendar 对象作为方法参数时,Jackson 能够将其正确反序列化为 Calendar 对象。

结论

如果您想将日历对象的 JSON 表示传递给 REST 端点,请将其作为长表示传递。

另一方面,如果您有 JavaScript 日期对象或自定义日期对象,您始终可以创建自定义反序列化器。

我知道这是一个老问题,但我只是添加了另一个构造函数,而不是 Calendar,所以它可以反序列化

@POST
public Response test(long c) {
    System.out.println(c);
    return Response.ok(Response.Status.OK).build();
}

例如:

public class demo{

    String id;
    Calendar date;
    String tag;

    public demo(Calendar date, String tag) {
        this.date = date;
        this.tag = tag;
    }

    public demo(long date, String tag) {
        this.date = longToCalendar(date);
        this.tag = tag;
    }

    public Calendar longToCalendar(Long epoch) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(epoch);
        return calendar;
    }
}


@POST
public Response test(Demo demo) {
    System.out.println(demo);
    return Response.ok(Response.Status.OK).build();
}