2013-08-19 7 views
65

У меня проблема в моем обычном десериализаторе в Джексоне. Я хочу получить доступ к сериализатору по умолчанию, чтобы заполнить объект, который я десериализую. После популяции я сделаю некоторые пользовательские вещи, но сначала хочу десериализовать объект с поведением джексона по умолчанию.Как я могу назвать десериализатор по умолчанию из пользовательского десериализатора в Jackson

Это код, который у меня есть на данный момент.

public class UserEventDeserializer extends StdDeserializer<User> { 

    private static final long serialVersionUID = 7923585097068641765L; 

    public UserEventDeserializer() { 
    super(User.class); 
    } 

    @Override 
    @Transactional 
    public User deserialize(JsonParser jp, DeserializationContext ctxt) 
     throws IOException, JsonProcessingException { 

    ObjectCodec oc = jp.getCodec(); 
    JsonNode node = oc.readTree(jp); 
    User deserializedUser = null; 
    deserializedUser = super.deserialize(jp, ctxt, new User()); 
    // The previous line generates an exception java.lang.UnsupportedOperationException 
    // Because there is no implementation of the deserializer. 
    // I want a way to access the default spring deserializer for my User class. 
    // How can I do that? 

    //Special logic 

    return deserializedUser; 
    } 

} 

Что мне нужно, это способ инициализации десериализации по умолчанию, так что я могу предварительно заполнить мой POJO, прежде чем начать свою особую логику.

При вызове десериализации из пользовательского десериализатора Швы метод вызывается из текущего контекста независимо от того, как я создаю класс сериализатора. Из-за аннотации в моем POJO. Это приводит к исключению переполнения стека по очевидным причинам. Я попытался инициализировать beandeserializer, но процесс чрезвычайно сложный, и мне не удалось найти правильный способ сделать это. Я также попытался перегрузить интроспектор аннотации, но ничего не понял, подумав, что это может помочь мне игнорировать аннотацию в DeserializerContext. Наконец, это швы, я, возможно, имел некоторый успех, используя JsonDeserializerBuilders, хотя это потребовало от меня сделать некоторые волшебные вещи, чтобы получить контекст приложения с весны. Я был бы признателен за любую вещь, которая могла бы привести меня к более чистому решению, например, как я могу создать контекст десериализации, не читая аннотацию JsonDeserializer.

+0

No. Эти подходы не помогут: проблема в том, что вам понадобится полностью сконструированный десериализатор по умолчанию; и для этого требуется, чтобы он строился, а затем ваш десериализатор получает к нему доступ. 'DeserializationContext' - это не то, что вы должны либо создавать, либо изменять; он будет предоставлен 'ObjectMapper'. 'AnnotationIntrospector', также не будет помогать в получении доступа. – StaxMan

+0

Как вы в конечном итоге сделали это в конце? – khituras

+0

Хороший вопрос. Я не уверен, но я уверен, что ответ ниже помог мне. В настоящее время я не владею кодом, который мы написали, если вы найдете решение, разместите его здесь для других. –

ответ

66

Как уже сообщал StaxMan, вы можете сделать это, написав BeanDeserializerModifier и зарегистрировав его через SimpleModule. Следующий пример должен работать:

public class UserEventDeserializer extends StdDeserializer<User> implements ResolvableDeserializer 
{ 
    private static final long serialVersionUID = 7923585097068641765L; 

    private final JsonDeserializer<?> defaultDeserializer; 

    public UserEventDeserializer(JsonDeserializer<?> defaultDeserializer) 
    { 
    super(User.class); 
    this.defaultDeserializer = defaultDeserializer; 
    } 

    @Override public User deserialize(JsonParser jp, DeserializationContext ctxt) 
     throws IOException, JsonProcessingException 
    { 
    User deserializedUser = (User) defaultDeserializer.deserialize(jp, ctxt); 

    // Special logic 

    return deserializedUser; 
    } 

    // for some reason you have to implement ResolvableDeserializer when modifying BeanDeserializer 
    // otherwise deserializing throws JsonMappingException?? 
    @Override public void resolve(DeserializationContext ctxt) throws JsonMappingException 
    { 
    ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt); 
    } 


    public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException 
    { 
    SimpleModule module = new SimpleModule(); 
    module.setDeserializerModifier(new BeanDeserializerModifier() 
    { 
     @Override public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) 
     { 
     if (beanDesc.getBeanClass() == User.class) 
      return new UserEventDeserializer(deserializer); 
     return deserializer; 
     } 
    }); 


    ObjectMapper mapper = new ObjectMapper(); 
    mapper.registerModule(module); 
    User user = mapper.readValue(new File("test.json"), User.class); 
    } 
} 
+0

Спасибо! Я уже решил это по-другому, но я рассмотрю ваше решение, когда у меня будет больше времени. –

+3

Есть ли способ сделать то же самое, но с помощью 'JsonSerializer'? У меня есть несколько сериализаторов, но у них есть общий код, поэтому я хочу его обобщить. Я пытаюсь напрямую вызвать Сериализатор, но результат не распаковывается в результате JSON (каждый вызов сериализатора создает новый объект) – herau

+1

Спасибо, человек, которого вы меня спасли! –

8

Существует несколько способов сделать это, но для этого нужно задействовать немного больше работы. В принципе, вы не можете использовать подклассификацию, поскольку требуемые десериализаторы, используемые по умолчанию, построены из определений классов.

Так что вы, скорее всего, можете использовать, это построить BeanDeserializerModifier, зарегистрируйтесь через интерфейс Module (используйте SimpleModule). Вам нужно определить/переопределить modifyDeserializer, а для конкретного случая, когда вы хотите добавить свою собственную логику (где совпадают типы), создайте собственный десериализатор, передайте десериализатор по умолчанию, который вы даете. И затем в методе deserialize() вы можете просто делегировать вызов, взять результат Object.

В качестве альтернативы, если вы действительно должны создать и заполнить объект, вы можете сделать это и вызвать перегруженную версию deserialize(), которая принимает третий аргумент; объект для десериализации.

Другим способом, который может работать (но не на 100% уверен), является указание объекта Converter (@JsonDeserialize(converter=MyConverter.class)). Это новая функция Jackson 2.2. В вашем случае Конвертер фактически не конвертирует тип, но упрощает модификацию объекта, но я не знаю, позволит ли это сделать именно то, что вы хотите, поскольку сначала будет вызываться десериализатор по умолчанию, а только ваш Converter.

+0

Я отредактировал мой вопрос, потому что я не мог вписать комментарий здесь. –

+0

Мой ответ по-прежнему стоит: вы должны позволить Jackson построить десериализатор по умолчанию для делегирования; и должны найти способ «переопределить» его. 'BeanDeserializerModifier' - это обработчик обратного вызова, который позволяет это. – StaxMan

0

Я не в порядке с помощью BeanSerializerModifier, поскольку она заставляет объявлять некоторые поведенческие изменения в центральной ObjectMapper, а не в обычае десериализатор себе и в том, что параллельно решение аннотирования класса сущностей с JsonSerialize ,Если вы чувствуете, что подобный путь, вы могли бы оценить мой ответ здесь: https://stackoverflow.com/a/43213463/653539

1

Более простым решением для меня было просто добавить еще один компонент из ObjectMapper и использовать его для десериализации объекта (спасибо https://stackoverflow.com/users/1032167/varren комментария) - в моем случае Я был заинтересован либо десериализации его идентификатор (целое число) или всего объект https://stackoverflow.com/a/46618193/986160

import com.fasterxml.jackson.annotation.JsonAutoDetect; 
import com.fasterxml.jackson.annotation.PropertyAccessor; 
import com.fasterxml.jackson.core.JsonParser; 
import com.fasterxml.jackson.core.JsonProcessingException; 
import com.fasterxml.jackson.databind.*; 
import com.fasterxml.jackson.databind.deser.std.StdDeserializer; 
import org.springframework.context.annotation.Bean; 

import java.io.IOException; 

public class IdWrapperDeserializer<T> extends StdDeserializer<T> { 

    private Class<T> clazz; 

    public IdWrapperDeserializer(Class<T> clazz) { 
     super(clazz); 
     this.clazz = clazz; 
    } 

    @Bean 
    public ObjectMapper objectMapper() { 
     ObjectMapper mapper = new ObjectMapper(); 
     mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 
     mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true); 
     mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); 
     mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); 
     mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); 
     return mapper; 
    } 

    @Override 
    public T deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException { 
     String json = jp.readValueAsTree().toString(); 
      // do your custom deserialization here using json 
      // and decide when to use default deserialization using local objectMapper: 
      T obj = objectMapper().readValue(json, clazz); 

      return obj; 
    } 
} 

для каждого объекта, который должен проходить через обычай десериализатор нам нужно настроить его в глобальной ObjectMapper боба Весеннего ботинка Приложение в моем случае (например, для Category):

@Bean 
public ObjectMapper objectMapper() { 
    ObjectMapper mapper = new ObjectMapper(); 
       mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 
      mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true); 
      mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); 
      mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); 
      mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); 
    SimpleModule testModule = new SimpleModule("MyModule") 
      .addDeserializer(Category.class, new IdWrapperDeserializer(Category.class)) 

    mapper.registerModule(testModule); 

    return mapper; 
} 
0

Вдоль линий какого Tomáš Záluský has suggested, в тех случаях, когда использование BeanDeserializerModifier нежелателен можно построить по умолчанию десериализатора самостоятельно, используя BeanDeserializerFactory, хотя есть некоторые дополнительные настройки необходимо. В контексте, это решение будет выглядеть так:

public User deserialize(JsonParser jp, DeserializationContext ctxt) 
    throws IOException, JsonProcessingException { 

    ObjectCodec oc = jp.getCodec(); 
    JsonNode node = oc.readTree(jp); 
    User deserializedUser = null; 

    DeserializationConfig config = ctxt.getConfig(); 
    JsonDeserializer<Object> defaultDeserializer = BeanDeserializerFactory.instance.buildBeanDeserializer(ctxt, User.class, config.introspect(User.class)); 

    if (defaultDeserializer instanceof ResolvableDeserializer) { 
     ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt); 
    } 

    JsonParser treeParser = oc.treeAsTokens(node); 
    config.initialize(treeParser); 

    if (treeParser.getCurrentToken() == null) { 
     treeParser.nextToken(); 
    } 

    deserializedUser = (User) defaultDeserializer.deserialize(treeParser, context); 

    return deserializedUser; 
} 
0

Если это возможно для вас, чтобы объявить дополнительный класс пользователя, то вы можете реализовать только с помощью аннотаций

// your class 
@JsonDeserialize(using = UserEventDeserializer.class) 
public class User { 
... 
} 

// extra user class 
// reset deserializer attribute to default 
@JsonDeserialize 
public class UserPOJO extends User { 
} 

public class UserEventDeserializer extends StdDeserializer<User> { 

    ... 
    @Override 
    public User deserialize(JsonParser jp, DeserializationContext ctxt) 
     throws IOException, JsonProcessingException { 
    // specify UserPOJO.class to invoke default deserializer 
    User deserializedUser = jp.ReadValueAs(UserPOJO.class); 
    return deserializedUser; 

    // or if you need to walk the JSON tree 

    ObjectMapper mapper = (ObjectMapper) jp.getCodec(); 
    JsonNode node = oc.readTree(jp); 
    // specify UserPOJO.class to invoke default deserializer 
    User deserializedUser = mapper.treeToValue(node, UserPOJO.class); 

    return deserializedUser; 
    } 

}