【问题标题】:Jackson deserialize based on typeJackson 基于类型反序列化
【发布时间】:2017-05-22 22:02:09
【问题描述】:

假设我有以下格式的 JSON:

{
    "type" : "Foo"
    "data" : {
        "object" : {
            "id" : "1"
            "fizz" : "bizz"
            ...
        },
        "metadata" : {
            ...
        },
        "owner" : {
            "name" : "John"
            ...
        }
    }
}

我试图避免使用自定义反序列化器并尝试将上述 JSON(称为 Wrapper.java)反序列化为 Java POJO。 “类型”字段指示“对象”反序列化,即。 type = foo 表示使用 Foo.java 反序列化“对象”字段。 (如果 type = Bar,使用 Bar.java 反序列化对象字段)。元数据/所有者将始终使用一个简单的 Jackson 注释 Java 类以相同的方式反序列化每个。有没有办法使用注释来实现这一点?如果没有,如何使用自定义反序列化器来完成?

【问题讨论】:

    标签: java json serialization jackson


    【解决方案1】:

    仅注释方法

    作为custom deserializer approach 的替代方案,您可以使用以下仅注释解决方案(类似于Spunc's answer 中描述的解决方案,但使用type 作为external property):

    public abstract class AbstractData {
    
        private Owner owner;
    
        private Metadata metadata;
    
        // Getters and setters
    }
    
    public static final class FooData extends AbstractData {
    
        private Foo object;
    
        // Getters and setters
    }
    
    public static final class BarData extends AbstractData {
    
        private Bar object;
    
        // Getters and setters
    }
    
    public class Wrapper {
    
        private String type;
    
        @JsonTypeInfo(use = Id.NAME, property = "type", include = As.EXTERNAL_PROPERTY)
        @JsonSubTypes(value = { 
                @JsonSubTypes.Type(value = FooData.class, name = "Foo"),
                @JsonSubTypes.Type(value = BarData.class, name = "Bar") 
        })
        private AbstractData data;
    
        // Getters and setters
    }
    

    在这种方法中,@JsonTypeInfo 设置为使用type 作为external property 来确定映射data 属性的正确类。

    JSON 文档可以反序列化如下:

    ObjectMapper mapper = new ObjectMapper();
    Wrapper wrapper = mapper.readValue(json, Wrapper.class);  
    

    【讨论】:

    • 我看到这种方法的问题是 BarData 和 FooData 现在与这个非常具体的 AbstractData 类相关联。想象一下,FooData 和 BarData 将用于代码库的完全不同的部分,它根本不关心它是如何构造的(来自 JSON)。
    • 稍微误读了您的代码,看起来这种方法绝对可行。对我来说剩下的痛点是每种类型的对象(Foo,Bar)现在都需要一个相应的 FooData 和 BarData 包装类,并且会导致为每种类型的“对象”被解析创建 2x 个类。有什么方法可以使用类型化泛型或类似的方法来解决这个问题?
    • @JohnBaum Generics 可以。但是,据我所知,在反序列化或在自定义反序列化器中处理它时,您需要定义 JavaTypeTypeReference。我的建议:不要过于复杂。
    • My solution 只需要一个超类,每种类型的对象只需要一个类。泛型将是矫枉过正。
    • 如何停止动态显示根的类型信息,知道我已经定义了@Generated("com.robohorse.robopojogenerator") @Entity @Table(name = "message") @JsonTypeInfo(use = Id.NAME, include = As.EXTERNAL_PROPERTY, property = "data") @JsonPropertyOrder({"id", "status", "numberOfUnits", "cost", "balance", "recipient", "dataCreated"}) @JsonSerialize(using = JacksonCustomMessageSerializer.class) @JsonDeserialize(using = JacksonCustomMessageDeserializer.class)
    【解决方案2】:

    自定义反序列化方法

    您可以使用检查type 属性的自定义反序列化程序将object 属性解析为最合适的类。

    首先定义一个由FooBar类实现的接口:

    public interface Model {
    
    }
    
    public class Foo implements Model {
    
        // Fields, getters and setters
    }
    
    public class Bar implements Model {
    
        // Fields, getters and setters
    }
    

    然后定义您的 WrapperData 类:

    public class Wrapper {
    
        private String type;
    
        private Data data;
    
        // Getters and setters
    }
    
    public class Data {
    
        @JsonDeserialize(using = ModelDeserializer.class)
        private Model object;
    
        private Metadata metadata;
    
        private Owner owner;
    
        // Getters and setters
    }
    

    object 字段用@JsonDeserialize 注释,表示将用于object 属性的反序列化器。

    反序列化器定义如下:

    public class ModelDeserializer extends JsonDeserializer<Model> {
    
        @Override
        public Model deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonMappingException {
    
            // Get reference to ObjectCodec
            ObjectCodec codec = jp.getCodec();
    
            // Parse "object" node into Jackson's tree model
            JsonNode node = codec.readTree(jp);
    
            // Get value of the "type" property
            String type = ((Wrapper) jp.getParsingContext().getParent()
                .getCurrentValue()).getType();
    
            // Check the "type" property and map "object" to the suitable class
            switch (type) {
    
                case "Foo":
                    return codec.treeToValue(node, Foo.class);
    
                case "Bar":
                    return codec.treeToValue(node, Bar.class);
    
                default:
                    throw new JsonMappingException(jp, 
                        "Invalid value for the \"type\" property");
            }
        }
    }
    

    JSON 文档可以反序列化如下:

    ObjectMapper mapper = new ObjectMapper();
    Wrapper wrapper = mapper.readValue(json, Wrapper.class);  
    

    除了这个自定义解串器,考虑annotations-only approach

    【讨论】:

      【解决方案3】:

      这一切都可以通过注解来完成。

      使用“元数据”和“所有者”等公共字段及其 getter/setter 创建一个抽象超类。这个类需要用@JsonTypeInfo注解。它应该看起来像:

      @JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY, property = "type")
      

      使用参数property = "type" 指定类标识符将在 JSON 文档中的字段 type 下序列化。

      类标识符的值可以用use 指定。 Id.CLASS 使用完全限定的 Java 类名。您还可以使用Id.MINIMAL_CLASS,它是Java 类的缩写名称。要拥有自己的标识符,请使用Id.NAME。在这种情况下,您需要声明子类型:

      @JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type")
      @JsonSubTypes({
          @JsonSubTypes.Type(value = Foo.class, name = "Foo"),
          @JsonSubTypes.Type(value = Bar.class, name = "Bar")
      })
      

      通过从抽象超类扩展来实现您的类 Foo 和 Bar。

      Jackson 的 ObjectMapper 将使用 JSON 文档的附加字段“类型”进行序列化和反序列化。例如。当您将 JSON 字符串反序列化为超类引用时,它将属于适当的子类:

      ObjectMapper om = new ObjectMapper();
      AbstractBase x = om.readValue(json, AbstractBase.class);
      // x will be instanceof Foo or Bar
      


      完整代码示例(我使用公共字段作为快捷方式,无需编写 getter/setter):

      package test;
      
      import com.fasterxml.jackson.annotation.JsonTypeInfo;
      import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
      import com.fasterxml.jackson.databind.ObjectMapper;
      import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
      
      import java.io.IOException;
      
      import com.fasterxml.jackson.annotation.JsonSubTypes;
      
      @JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type")
      @JsonSubTypes({
          @JsonSubTypes.Type(value = Foo.class, name = "Foo"),
          @JsonSubTypes.Type(value = Bar.class, name = "Bar")
      })
      public abstract class AbstractBase {
      
          public MetaData metaData;
          public Owner owner;
          @Override
          public String toString() {
              return "metaData=" + metaData + "; owner=" + owner;
          }
      
          public static void main(String[] args) throws IOException {
      
              // Common fields
              Owner owner = new Owner();
              owner.name = "Richard";
              MetaData metaData = new MetaData();
              metaData.data = "Some data";
      
              // Foo
              Foo foo = new Foo();
              foo.owner = owner;
              foo.metaData = metaData;
              CustomObject customObject = new CustomObject();
              customObject.id = 20l;
              customObject.fizz = "Example";
              Data data = new Data();
              data.object = customObject;
              foo.data = data;
              System.out.println("Foo: " + foo);
      
              // Bar
              Bar bar = new Bar();
              bar.owner = owner;
              bar.metaData = metaData;
              bar.data = "A String in Bar";
      
              ObjectMapper om = new ObjectMapper();
      
              // Test Foo:
              String foojson = om.writeValueAsString(foo);
              System.out.println(foojson);
              AbstractBase fooDeserialised = om.readValue(foojson, AbstractBase.class);
              System.out.println(fooDeserialised);
      
              // Test Bar:
              String barjson = om.writeValueAsString(bar);
              System.out.println(barjson);
              AbstractBase barDeserialised = om.readValue(barjson, AbstractBase.class);
              System.out.println(barDeserialised);
      
          }
      
      }
      
      class Foo extends AbstractBase {
          public Data data;
          @Override
          public String toString() {
              return "Foo[" + super.toString() + "; data=" + data + ']';
          }
      }
      
      class Bar extends AbstractBase {
          public String data;
          public String toString() {
              return "Bar[" + super.toString() + "; data=" + data + ']';
          }
      }
      
      
      class Data {
          public CustomObject object;
          @Override
          public String toString() {
              return "Data[object=" + object + ']';
          }
      }
      
      class CustomObject {
          public long id;
          public String fizz;
          @Override
          public String toString() {
              return "CustomObject[id=" + id + "; fizz=" + fizz + ']';
          }
      }
      
      class MetaData {
          public String data;
          @Override
          public String toString() {
              return "MetaData[data=" + data + ']';
          }
      }
      
      class Owner {
          public String name;
          @Override
          public String toString() {
              return "Owner[name=" + name + ']';
          }
      }
      

      【讨论】:

      • 看起来它不适用于 OP 情况,因为 type 未在 object 下定义。因此@JsonTypeInfo 将不起作用。
      • 已经在生产环境中使用(当然还有单元测试)。物业type 将由杰克逊介绍。如果您在类本身中有一个 type 字段,它将不起作用。
      • 如果可能的话,我很想看看@JsonTypeInfo 的例子,它适用于OP 提到的情况。
      • @JsonTypeInfo(use = Id.NAME, property = "type", include = As.EXTERNAL_PROPERTY) 可以工作。有机会我会试试的。
      • 看起来您的代码生成了{"type":"Foo","metaData":{"data":"Some data"},"owner":{"name":"Richard"},"data":{"object":{"id":20,"fizz":"Example"}}},它与问题中显示的 JSON 略有不同。可以通过将metaDataowner 成员从AbstractBase 移动到Data 类来解决。
      【解决方案4】:

      我认为这是相当直截了当的。您可能有一个具有metadataowner 属性的超类,因此您可以用T 代替您的超类,而不是使其真正通用。但基本上,您必须从实际的 JSON 字符串中解析类的名称,在您的示例中看起来像这样:

      int start = jsonString.indexOf("type");
      int end = jsonString.indexOf("data");
      Class actualClass = Class.forName(jsonString.substring(start + 4, end - 2)); // that of course, is approximate - based on how you format JSON
      

      整体代码可能是这样的:

      public static <T> T deserialize(String xml, Object obj)
              throws JAXBException {
      
          T result = null;
      
          try {
      
              int start = jsonString.indexOf("type");
              int end = jsonString.indexOf("data");
              Class actualClass = Class.forName(jsonString.substring(start + 4, end - 2)); 
      
              JAXBContextFactory factory = JAXBContextFactory.getInstance();
              JAXBContext jaxbContext = factory.getJaxBContext(actualClass);
      
              Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
      
              // this will create Java object
              try (StringReader reader = new StringReader(xml)) {
                  result = (T) jaxbUnmarshaller.unmarshal(reader);
              }
      
          } catch (JAXBException e) {
              log.error(String
                      .format("Exception while deserialising the object[JAXBException] %s\n\r%s",
                              e.getMessage()));
          }
      
          return result;
      }
      

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-18
      • 2012-10-18
      • 2012-07-24
      • 2014-05-01
      • 2014-08-07
      • 1970-01-01
      相关资源
      最近更新 更多