【问题标题】:Jersey + Jackson JSON date format serialization - how to change the format or use custom JacksonJsonProviderJersey + Jackson JSON 日期格式序列化 - 如何更改格式或使用自定义 JacksonJsonProvider
【发布时间】:2011-05-24 14:36:04
【问题描述】:

我正在使用 Jersey + Jackson 为我的应用程序提供 REST JSON 服务层。我遇到的问题是默认的日期序列化格式如下所示:

"CreationDate":1292236718456

起初我以为它是一个 UNIX 时间戳……但它太长了。我的客户端 JS 库在反序列化这种格式时遇到了问题(它支持一堆不同的日期格式,但我想不支持这种格式)。我想更改格式,以便我的库可以使用它(例如 ISO)。我该怎么做...我找到了一段可以提供帮助的代码,但是...我应该把它放在哪里,因为我不控制 Jackson 序列化程序实例化(Jersey 可以)?

objectMapper.configure(
    SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);

我还为自定义 JacksonJsonProvider 找到了此代码 - 问题是 .. 如何让我的所有 POJO 类都使用它?

@Provider
public class MessageBodyWriterJSON extends JacksonJsonProvider {

    private static final String DF = "yyyy-MM-dd’T'HH:mm:ss.SSSZ";

    @Override
    public boolean isWriteable(Class arg0, Type arg1, Annotation[] arg2,
            MediaType arg3) {
        return super.isWriteable(arg0, arg1, arg2,
                arg3);
    }
    @Override
    public void writeTo(Object target, Class arg1, Type arg2, Annotation[] arg3,
            MediaType arg4, MultivaluedMap arg5, OutputStream outputStream)
            throws IOException, WebApplicationException {
            SimpleDateFormat sdf=new SimpleDateFormat(DF);

        ObjectMapper om = new ObjectMapper();
        om.getDeserializationConfig().setDateFormat(sdf);
        om.getSerializationConfig().setDateFormat(sdf);
        try {
            om.writeValue(outputStream, target);
        } catch (JsonGenerationException e) {
            throw e;
        } catch (JsonMappingException e) {
            throw e;
        } catch (IOException e) {
            throw e;
        }
    }
}

【问题讨论】:

  • 我发现这是 Jersey 1.1.5 和 Jersey 1.6 之间的变化 - 对于 Jersey 1.1.5,JSON 序列化如下所示:{"date":"2011-04-01T16:41 :18.707+00:00"} - 可能有一个提供背景信息的问题跟踪器项目
  • 是的,管道和设置是最耗时的部分。我还没有弄明白。我建议只将日期输出为字符串,然后让您的客户处理将字符串转换为日期。这是关于用代码代替配置。我宁愿编写“在我的控制范围内”的代码,而不是我知道我可以控制但到目前为止似乎不受我控制的配置奥秘。如果没有明确的示例,配置很棘手,因此碰巧使用了您正在使用的相同 java 工具包。可移植性是一个有价值的目标,但确实很难。

标签: java serialization jersey jax-rs jackson


【解决方案1】:

我设法在 Resteasy“JAX-RS 方式”中做到这一点,因此它应该适用于像 Jersey 这样的每个兼容实现(最近在 JEE7 服务器 Wildfly 8 上成功测试,它只需要对 Jackson 部分进行一些更改,因为它们更改了一些 API)。

您必须定义一个 ContextResolver(检查 Produces 是否包含正确的内容类型):

import javax.ws.rs.ext.ContextResolver;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.DeserializationConfig;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.Produces; 
import java.text.SimpleDateFormat;
@Provider
@Produces("application/json")
public class JacksonConfigurator implements ContextResolver<ObjectMapper> {

    private ObjectMapper mapper = new ObjectMapper();

    public JacksonConfigurator() {
        SerializationConfig serConfig = mapper.getSerializationConfig();
        serConfig.setDateFormat(new SimpleDateFormat(<my format>));
        DeserializationConfig deserializationConfig = mapper.getDeserializationConfig();
        deserializationConfig.setDateFormat(new SimpleDateFormat(<my format>));
        mapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

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

}

那么你必须在你的 javax.ws.rs.core.Application 的 getClasses 中返回新创建的类

import javax.ws.rs.core.Application;
public class RestApplication extends Application {

     @Override
     public Set<Class<?>> getClasses() {
         Set<Class<?>> classes = new HashSet<Class<?>>();
         // your classes here
         classes.add(JacksonConfigurator.class);
         return classes;
      }

}

这样,通过 jackson 进行的所有操作都将获得您选择的 ObjectMapper。

编辑:我最近发现,如果您决定扩展 JacksonConfigurator 以添加自定义映射,则使用 RestEasy 2.0.1(以及 Jackson 1.5.3)会出现奇怪的行为。

import javax.ws.rs.core.MediaType;
@Provider
@Produces(MediaType.APPLICATION_JSON)
public class MyJacksonConfigurator extends JacksonConfigurator

如果您只是这样做(当然将扩展类放在 RestApplication 中),则使用父类的映射器,那就是您丢失了自定义映射。为了让它正常工作,我不得不做一些对我来说似乎没用的事情:

public class MyJacksonConfigurator extends JacksonConfigurator implements ContextResolver<ObjectMapper> 

【讨论】:

  • SimpleDateFormat 不适合多线程环境(我已经看到这会产生讨厌的错误)。有谁知道配置如何使用 SimpleDataFormat?
  • 是的,我知道(尽管我总是惊讶地发现有多少“经验丰富的”开发人员不知道这一点);我查看了文档和代码,提供的实例仅用作蓝图,它总是在使用前被克隆。不确定这是来自 Jackson 开发人员的一个很好的选择,但他们的 API 需要 DateFormat,这不能保证是线程安全的,他们通过克隆它们来考虑这一点。无论如何,感谢您的好评。
  • 我试过这个,但 getClasses() 和 getContext() 都没有在我的项目中被调用。关于我可能做错的任何提示? web.xml 中还有什么需要设置的吗?
  • 我添加了它们,但我手头没有原始来源,所以仔细检查它们不是一件容易的事;请你帮我做一下好吗?鉴于范围狭窄(jax-rs 或 jackson),填补空白(如果有的话)应该不难
  • 请注意,覆盖 getClasses 的副作用是 Jersey 会停止扫描资源类或提供程序,因此您必须将它们明确包含在 getClasses 中。至少对于 jersey-container-servlet 2.25.1,添加 @Provider 注释类就足够了,该类会自动获取 - 无需覆盖 Application。
【解决方案2】:

要配置自己的ObjectMapper,需要注入自己的实现ContextResolver的类

具体如何让球衣拾起这将取决于您的 IOC(spring, guice)。我使用 spring,我的类看起来像这样:

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

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig.Feature;
import org.codehaus.jackson.map.deser.CustomDeserializerFactory;
import org.codehaus.jackson.map.deser.StdDeserializerProvider;
import org.codehaus.jackson.map.ser.CustomSerializerFactory;
import org.springframework.stereotype.Component;

// tell spring to look for this.
@Component
// tell spring it's a provider (type is determined by the implements)
@Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
    @Override
    public ObjectMapper getContext(Class<?> type) {
        // create the objectMapper.
        ObjectMapper objectMapper = new ObjectMapper();
        // configure the object mapper here, eg.
           objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
        return objectMapper;
    }
}

【讨论】:

    【解决方案3】:

    对于它的价值,这个数字是标准的 Java 时间戳(由 JDK 类使用); Unix 存储秒,Java 毫秒,这就是为什么它的值有点大。

    我希望有一些关于如何将 ObjectMapper 注入 Jersey 的文档(它应该遵循通常的方式来注入提供的对象)。但或者,您可以覆盖 JacksonJaxRsProvider 以指定/配置 ObjectMapper 并注册它;这就是泽西岛本身所做的事情,并且有多种方法可以做到。

    【讨论】:

    • 非常感谢您的提示。问题是我似乎找不到任何有关如何执行此操作的文档。
    【解决方案4】:

    如果您选择在服务器上使用 Joda DateTime 对象并希望序列化为 ISO8601,您可以使用 Jackson's JodaModule。您可以按如下方式注册 Jersey Provider:

    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.datatype.joda.JodaModule;
    
    @Provider
    public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
    
      final ObjectMapper objectMapper;
    
      public MyObjectMapperProvider() {
        objectMapper = new ObjectMapper();
        /* Register JodaModule to handle Joda DateTime Objects. */
        objectMapper.registerModule(new JodaModule());
        /* We want dates to be treated as ISO8601 not timestamps. */
        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
      }
    
      @Override
      public ObjectMapper getContext(Class<?> arg0) {
        return objectMapper;
      }
    }
    

    更多信息请访问Jersey's website

    【讨论】:

    • 这是上面的 FasterXML 版本。
    【解决方案5】:

    我遇到了同样的问题(使用 Jersey+Jackson+Json),客户端正在发送一个日期,但是当数据映射到对象时它在服务器中被更改。

    当我意识到收到的日期是时间戳时,我通过阅读此链接:http://blog.bdoughan.com/2010/07/xmladapter-jaxbs-secret-weapon.html,采用了其他方法来解决这个问题(与他的问题中的 Adrin 相同:"creationDate":1292236718456

    在我的 VO 类中,我将此注释添加到属性 @XmlJavaTypeAdapter 并实现了一个扩展 XmlAdapter 的内部类:

    @XmlRootElement
    public class MyClassVO {
       ...
       @XmlJavaTypeAdapter(DateFormatterAdapter.class) 
       Date creationDate;
       ...
    
       private static class DateFormatterAdapter extends XmlAdapter<String, Date> {
          @Override
          public Date unmarshal(final String v) throws Exception {
             Timestamp stamp = new Timestamp(new Long(v));
             Date date = new Date(stamp.getTime());
             return date;
          }
    }
    

    希望对你也有帮助。

    【讨论】:

      【解决方案6】:

      以下代码对我有用 - JAX-RS 1.1、Jersy 1.8

      import java.text.SimpleDateFormat;
      
      import javax.ws.rs.Produces;
      import javax.ws.rs.core.MediaType;
      import javax.ws.rs.ext.Provider;
      
      import org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider;
      import org.codehaus.jackson.map.DeserializationConfig;
      import org.codehaus.jackson.map.ObjectMapper;
      import org.codehaus.jackson.map.SerializationConfig;
      import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion;
      
      
      @Provider
      @Produces(MediaType.APPLICATION_JSON)
      public class JsonProvider extends JacksonJaxbJsonProvider {
        private static final ObjectMapper objectMapper = new ObjectMapper();
        static {
          // allow only non-null fields to be serialized
          objectMapper.getSerializationConfig().setSerializationInclusion(Inclusion.NON_NULL);
      
          SerializationConfig serConfig = objectMapper.getSerializationConfig();
          serConfig.setDateFormat(new SimpleDateFormat(<your date format>));
          DeserializationConfig deserializationConfig = objectMapper.getDeserializationConfig();
          deserializationConfig.setDateFormat(new SimpleDateFormat(<your date format>));
          objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
      
        }
      
        public JsonProvider() {
          super.setMapper(objectMapper);
        }
      }
      

      【讨论】:

        【解决方案7】:

        用这个重写 MessageBodyWriterJSON

        import javax.ws.rs.core.MediaType; 
        import javax.ws.rs.ext.Provider; 
        
        import org.codehaus.jackson.jaxrs.JacksonJsonProvider; 
        import org.codehaus.jackson.map.ObjectMapper; 
        import org.codehaus.jackson.map.SerializationConfig; 
        
        @Provider 
        public class MessageBodyWriterJSON extends JacksonJsonProvider { 
                    public MessageBodyWriterJSON (){ 
                    } 
        
                @Override 
                    public ObjectMapper locateMapper(Class<?> type, MediaType mediaType) 
                { 
                ObjectMapper mapper = super.locateMapper(type, mediaType); 
                //DateTime in ISO format "2012-04-07T17:00:00.000+0000" instead of 'long' format 
                    mapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false); 
                    return mapper; 
                } 
        }
        

        【讨论】:

        • 然后呢?你如何让泽西岛使用它?
        【解决方案8】:

        json-io (https://github.com/jdereg/json-io) 是一个完整的 Java 到/从 JSON 序列化库。在使用它编写 JSON 字符串时,您可以设置 如何 日期的格式。默认情况下,写出的日期很长(如上所述,即自 1970 年 1 月 1 日以来的毫秒数)。但是,您可以给它一个格式 String 或 Java DateFormatter 并以您希望的任何格式写入日期。

        【讨论】:

          猜你喜欢
          • 2020-02-12
          • 2016-02-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-02-07
          • 2016-04-11
          相关资源
          最近更新 更多