【问题标题】:Jackson deserialize object or arrayJackson 反序列化对象或数组
【发布时间】:2011-11-29 03:42:18
【问题描述】:

我有一个杰克逊问题。

有没有办法反序列化可能有两种类型的属性,对于某些对象,它看起来像这样

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

那么对于其他人它显示为一个空数组,即

"someObj" : []

感谢任何帮助!

谢谢!

【问题讨论】:

  • 我发现 this 回答如果您想在单个属性上执行此操作很有用

标签: java json jackson


【解决方案1】:

编辑:从 Jackson 2.5.0 开始,您可以使用 DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_EMPTY_OBJECT 来解决您的问题。

Bruce 提供的解决方案有一些问题/缺点:

  • 您需要为需要以这种方式反序列化的每种类型复制该代码
  • ObjectMapper 应该被重用,因为它缓存了序列化器和反序列化器,因此创建起来很昂贵。见http://wiki.fasterxml.com/JacksonBestPracticesPerformance
  • 如果您的数组包含一些值,您可能希望让 jackson 对其进行反序列化失败,因为这意味着它在编码时出现问题,您应该尽快查看并修复该问题。

这是我针对该问题的“通用”解决方案:

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 个自定义反序列化器,我会全力以赴 ;)

【讨论】:

  • 这个解决方案看起来像我需要的......谢谢......但是当我尝试使用它时,杰克逊抛出了一个 JsonMappingException: has no default (no arg) constructor。我尝试向基类添加默认构造函数,但没有任何运气。
  • 确保传递给 jackson 以反序列化的类具有公共默认构造函数(不仅仅是它的基类)。在上面的示例中,我使用 Thing2.class 反序列化; Thing2 的类有一个公共的默认构造函数。确保您尝试反序列化的类的所有子类也具有公共默认构造函数。有疑问,请尝试使用只有原始类型的更简单的类,如果它有效,您就知道这是您的一个类的问题;)
【解决方案2】:

Jackson 目前没有内置配置来自动处理这种特殊情况,因此需要自定义反序列化处理。

以下是此类自定义反序列化的示例。

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 网站。
  • > 您是否碰巧知道有关编写一个的好教程? -- 用示例更新答案。
  • 从杰克逊 2.5.0 开始你可以使用 DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_EMPTY_OBJECT 见@fabien 回复
【解决方案3】:

对于将使用 BeanDeserializer 反序列化的对象,还有另一个角度更通用地解决此问题,方法是创建 BeanDeserializerModifier 并将其注册到您的映射器。 BeanDeserializerModifier 是子类化 BeanDeserializerFactory 的一种替代方法,它使您有机会返回将使用的普通反序列化器以外的东西,或对其进行修改。

所以,首先创建一个新的JsonDeserializer,它可以在构建时接受另一个反序列化器,然后保留该序列化器。在反序列化方法中,您可以检查是否传递了当前指向 JsonToken.START_ARRAYJsonParser。如果您没有通过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);
        }
    }
}

现在我们有了通用的反序列化器钩子,让我们创建一个可以使用它的修饰符。这很简单,只需在 BeanDeserializerModifier 中实现 modifyDeserializer 方法即可。您将获得 用于反序列化 bean 的反序列化器。它还会向您传递将被反序列化的 BeanDesc,因此您可以在此处控制是否要将所有类型的 [] 处理为 null。

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

【讨论】:

    【解决方案4】:

    其他答案都不适合我:

    • 我们不能使用修饰符,因为 ObjectMapper 不能被修改,我们使用 @JsonDeserialize 注释来安装反序列化器。
    • 我们也无权访问ObjectMapper
    • 我们需要正确输入生成的Map,这似乎不适用于ObjectCodec.treeToValue

    这是最终奏效的解决方案:

    public class EmptyArrayToEmptyMapDeserializer extends JsonDeserializer<Map<String, SomeComplexType>> {
        @Override
        public Map<String, SomeComplexType> deserialize(JsonParser parser,
                DeserializationContext context) throws IOException {
            if (parser.getCurrentToken() == JsonToken.START_ARRAY) {
                // Not sure what the parser does with the following END_ARRAY token, probably ignores it somehow.
                return Map.of();
            }
            return context.readValue(parser, TypeFactory.defaultInstance().constructMapType(Map.class, String.class, SomeComplexType.class));
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-10-05
      • 2011-09-15
      • 1970-01-01
      • 2011-06-28
      • 1970-01-01
      • 2020-08-07
      相关资源
      最近更新 更多