Джексон десериализует объект или массив - PullRequest
14 голосов
/ 29 ноября 2011

У меня есть вопрос Джексона.

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

"someObj" : { "obj1" : 5, etc....}

тогда для других он выглядит как пустой массив, т.е.

"someObj" : []

Любая помощь приветствуется!

Спасибо!

Ответы [ 3 ]

20 голосов
/ 09 апреля 2014

Редактировать: Начиная с Jackson 2.5.0, вы можете использовать DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_EMPTY_OBJECT для решения вашей проблемы.

Решение, которое предлагает Брюс, имеет несколько проблем / недостатков:

  • вам нужно будет продублировать этот код для каждого типа, который вам нужен для десериализации таким образом
  • ObjectMapper следует использовать повторно, поскольку он кэширует сериализаторы и десериализаторы и, следовательно, дорог в создании. Смотри http://wiki.fasterxml.com/JacksonBestPracticesPerformance
  • если ваш массив содержит некоторые значения, вы, вероятно, хотите, чтобы Джексон не смог десериализовать его, потому что это означает, что при его кодировании возникла проблема, и вы должны увидеть и исправить это как можно скорее.

Вот мое «общее» решение этой проблемы:

public abstract class EmptyArrayAsNullDeserializer<T> extends JsonDeserializer<T> {

  private final Class<T> clazz;

  protected EmptyArrayAsNullDeserializer(Class<T> clazz) {
    this.clazz = clazz;
  }

  @Override
  public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
    ObjectCodec oc = jp.getCodec();
    JsonNode node = oc.readTree(jp);
    if (node.isArray() && !node.getElements().hasNext()) {
      return null;
    }
    return oc.treeToValue(node, clazz);
  }
}

тогда вам все еще нужно создать собственный десериализатор для каждого отдельного типа, но это гораздо проще написать, и вы не дублируете никакой логики:

public class Thing2Deserializer extends EmptyArrayAsNullDeserializer<Thing2> {

  public Thing2Deserializer() {
    super(Thing2.class);
  }
}

тогда вы используете его как обычно:

@JsonDeserialize(using = Thing2Deserializer.class)

Если вы найдете способ избавиться от этого последнего шага, т.е. реализуя 1 пользовательский десериализатор для каждого типа, я весь в ушах;)

14 голосов
/ 29 ноября 2011

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

Ниже приведен пример того, как может выглядеть такая пользовательская десериализация.

import java.io.IOException;

import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonMethod;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.module.SimpleModule;

public class JacksonFoo
{
  public static void main(String[] args) throws Exception
  {
    // {"property1":{"property2":42}}
    String json1 = "{\"property1\":{\"property2\":42}}";

    // {"property1":[]}
    String json2 = "{\"property1\":[]}";

    SimpleModule module = new SimpleModule("", Version.unknownVersion());
    module.addDeserializer(Thing2.class, new ArrayAsNullDeserializer());

    ObjectMapper mapper = new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY).withModule(module);

    Thing1 firstThing = mapper.readValue(json1, Thing1.class);
    System.out.println(firstThing);
    // output:
    // Thing1: property1=Thing2: property2=42

    Thing1 secondThing = mapper.readValue(json2, Thing1.class);
    System.out.println(secondThing);
    // output: 
    // Thing1: property1=null
  }
}

class Thing1
{
  Thing2 property1;

  @Override
  public String toString()
  {
    return String.format("Thing1: property1=%s", property1);
  }
}

class Thing2
{
  int property2;

  @Override
  public String toString()
  {
    return String.format("Thing2: property2=%d", property2);
  }
}

class ArrayAsNullDeserializer extends JsonDeserializer<Thing2>
{
  @Override
  public Thing2 deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException
  {
    JsonNode node = jp.readValueAsTree();
    if (node.isObject())
      return new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY).readValue(node, Thing2.class);
    return null;
  }
}

(Вы можете использовать DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, чтобы заставить вход всегда связываться с коллекцией, но, вероятно, это не тот подход, который я выбрал бы, учитывая, как в настоящее время описывается проблема.)

2 голосов
/ 31 октября 2014

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

Итак, сначала создайте новый JsonDeserializer, который может принимать другой десериализатор при его создании, а затем удерживает этот сериализатор.В методе десериализации вы можете проверить, передали ли вы JsonParser, который в данный момент указывает на JsonToken.START_ARRAY.Если вы не прошли JsonToken.START_ARRAY, просто используйте десериализатор по умолчанию, который был передан этой пользовательской десериализации при ее создании.

Наконец, убедитесь, что реализован ResolvableDeserializer, чтобы десериализатор по умолчанию был правильно подключен к контексту, который использует ваш пользовательский десериализатор.

class ArrayAsNullDeserialzer extends JsonDeserializer implements ResolvableDeserializer {
    JsonDeserializer<?> mDefaultDeserializer;

    @Override
    /* Make sure the wrapped deserializer is usable in this deserializer's contexts */
    public void resolve(DeserializationContext ctxt) throws JsonMappingException  {
         ((ResolvableDeserializer) mDefaultDeserializer).resolve(ctxt);
    }

    /* Pass in the deserializer given to you by BeanDeserializerModifier */
    public ArrayAsNullDeserialzer(JsonDeserializer<?> defaultDeserializer) {
        mDefaultDeserializer = defaultDeserializer;
    }

    @Override
    public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonToken firstToken = jp.getCurrentToken();
        if (firstToken == JsonToken.START_ARRAY) {
            //Optionally, fail if this is something besides an empty array
           return null;
        } else {
            return mDefaultDeserializer.deserialize(jp, ctxt);
        }
    }
}

Теперь, когда у нас есть универсальный десериализатор, давайте создадим модификатор, который сможет его использовать.Это просто, просто реализуйте метод modifyDeserializer в вашем BeanDeserializerModifier.Вам передадут десериализатор, который будет использован для десериализации бина.Он также передает вам BeanDesc, который будет десериализован, так что вы можете здесь контролировать, хотите ли вы обрабатывать [] как ноль для всех типов.

public class ArrayAsNullDeserialzerModifier extends BeanDeserializerModifier  {

    @Override
    public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
        if ( true /* or check beanDesc to only do this for certain types, for example */ ) {
            return new ArrayAsNullDeserializer(deserializer);
        } else {
            return deserializer;
        }
    }
}

Наконец, вам нужно зарегистрировать свой BeanDeserializerModifier в ObjectMapper.Чтобы сделать это, создайте модуль и добавьте модификатор в настройку (к сожалению, SimpleModules, похоже, не имеют зацепки для этого).Вы можете прочитать больше о модулях в другом месте, но вот пример, если у вас еще нет модуля для добавления:

Module m = new Module() {
    @Override public String getModuleName() { return "MyMapperModule"; }
    @Override public Version version() { return Version.unknownVersion(); }
    @Override public void setupModule(Module.SetupContext context) {
        context.addBeanDeserializerModifier(new ArrayAsNullDeserialzerModifier());
    }
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...