Джерси + Джексон JSON формат даты формат - как изменить формат или использовать пользовательские JacksonJsonProvider

Я использую Jersey + Jackson для предоставления уровня сервиса REST JSON для моего приложения. Проблема заключается в том, что формат сериализации по умолчанию выглядит так:

"CreationDate":1292236718456

Сначала я думал, что это временная метка UNIX... но это слишком долго для этого. У моей клиентской JS-библиотеки есть проблемы, десериализирующие этот формат (он поддерживает кучу разных форматов дат, но не этот, я полагаю). Я хочу изменить формат, чтобы он мог потребляться моей библиотекой (например, по ISO). Как это сделать... Я нашел фрагмент кода, который мог бы помочь, но... где я могу это выразить, поскольку я не контролирую экземпляр Jackson serializer (Джерси)?

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

Ответ 1

Для чего это стоит, это число является стандартной меткой времени Java (используется классами JDK); Unix хранит секунды, миллисекунды Java, поэтому он бит большего значения.

Я надеюсь, что есть некоторые документы о том, как вводить ObjectMapper в Джерси (он должен следовать обычному способу ввода объекта). Но в качестве альтернативы вы можете переопределить JacksonJaxRsProvider, чтобы указать/настроить ObjectMapper и зарегистрировать это; это то, что делает сам Джерси, и есть несколько способов сделать это.

Ответ 2

Мне удалось сделать это в Restaasy "JAX-RS way", поэтому он должен работать на любой совместимой реализации, такой как Jersey (недавно успешно протестированный на JEE7-сервере Wildfly 8), просто потребовалось несколько изменений в части Джексона, потому что они изменилось несколько 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 по вашему выбору.

EDIT: Недавно я выяснил, что с помощью RestEasy 2.0.1 (и, следовательно, Jackson 1.5.3) возникает странное поведение, если вы решите расширить JacksonConfigurator для добавления пользовательских сопоставлений.

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> 

Ответ 3

Чтобы настроить собственный ObjectMapper, вам нужно ввести свой собственный класс, который реализует ContextResolver <ObjectMapper>

Точно, как получить майку, чтобы забрать ее, будет зависеть от вашего 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 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;
    }
}

Ответ 4

У меня была такая же проблема (с использованием Jersey + Jackson + Json), клиент отправил дату, но она была изменена на сервере, когда данные были сопоставлены с объектом.

Я воспользовался другим подходом, чтобы решить эту проблему, прочитав эту ссылку: http://blog.bdoughan.com/2010/07/xmladapter-jaxbs-secret-weapon.html, когда я понял, что полученная дата была TimeStamp ( то же, что и у Адрина в его вопросе: "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;
      }
}

Надеюсь, это тоже поможет вам.

Ответ 5

Перезапишите 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; 
        } 
}

Ответ 6

Если вы решили работать с объектами Joda DateTime на своем сервере и хотите сериализоваться на ISO8601, вы можете использовать Jackson JodaModule. Вы можете зарегистрировать Поставщик Джерси следующим образом:

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

Дополнительная информация доступна на сайте Джерси.

Ответ 7

json-io (https://github.com/jdereg/json-io) - это полная библиотека Java для JSON-сериализации. Когда вы используете его для записи строки JSON, вы можете указать, как отформатировать даты. По умолчанию даты выписываются так долго (как указано выше, с миллисекунд с 1 января 1970 г.). Тем не менее, вы можете дать ему формат String или Java DateFormatter и указать даты, записанные в любом формате.