【问题标题】:Deserialize string using custom deserializer specified in field of class使用在类字段中指定的自定义反序列化器反序列化字符串
【发布时间】:2019-02-24 22:47:42
【问题描述】:

我需要编写一个方法来获取一些对象、给定对象的类中存在的一些字段名称fieldName,以及一些字段值value。该值是字段的 JSON 序列化形式。该方法应获取该值并相应地对其进行反序列化,如下所示:

static void setField(Object obj, String fieldName, String value) throws Exception {
    Field field = obj.getClass().getDeclaredField(fieldName)
    Object valObj = objectMapper.readValue(value, field.getType());
    field.set(obj, valObj);
}

(我实际上只需要检索反序列化的值,而不是再次设置它,但这使它成为一个更好的例子。) 只要杰克逊的默认反序列化足够,就可以了。现在假设我有一个带有自定义(反)序列化器的类:

class SomeDTO {
    String foo;
    @JsonSerialize(using = CustomInstantSerializer.class)
    @JsonDeserialize(using = CustomInstantDeserializer.class)
    Instant bar;
}

一种可能的解决方案是手动检查JsonDeserialize 注释。但是,我真的不想尝试复制 Jackson 遵循的任何策略来决定使用什么序列化程序,因为这看起来很脆弱(例如全局注册的序列化程序)。

有没有使用 DTO 类中定义的字段的反序列化配置来反序列化值的好方法?也许在将字段的注释传递给 Jackson 的同时将值反序列化为字段的类型,以便他们得到尊重?

我设法获得了一个 AnnotatedMember 实例,该实例包含所有必需的信息(JSON 注释和反射字段或 setter/getter 访问),但不知道如何使用它由于缺乏文档而反序列化独立值:

final JavaType dtoType = objectMapper.getTypeFactory().constructType(SomeDTO.class);
final BeanDescription description = objectMapper.getDeserializationConfig().introspect(dtoType);
for (BeanPropertyDefinition propDef: beanDescription.findProperties()) {
    final AnnotatedMember mutator = propertyDefinition.getNonConstructorMutator();
    // now what? Also: How do I filter for the correct property?
}

【问题讨论】:

  • 你能提供你想要达到的目标的示例 JSON 输出吗?没有输出部分似乎有点复杂理解

标签: java serialization jackson


【解决方案1】:

一种可能性是序列化对象,替换给定字段,然后再次反序列化。这可以在从/到JsonNode 而不是 JSON-String 序列化时轻松完成,如下所示:

static Object setField(Object obj, String fieldName, String value) throws Exception {
    // note: produces a new object instead of modifying the existing one
    JsonNode node = objectMapper.valueToTree(obj);
    ((ObjectNode) node).put(fieldName, value);
    return objectMapper.readValue(node.traverse(), obj.getClass());
}

然而,序列化和反序列化整个对象只是为了反序列化单个字段似乎有很多开销,并且可能很脆弱,因为 DTO 类的其他方面会影响单个字段的反序列化过程

【讨论】:

    【解决方案2】:
    import com.fasterxml.jackson.core.JsonGenerator;
    import com.fasterxml.jackson.core.JsonParser;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.DeserializationContext;
    import com.fasterxml.jackson.databind.DeserializationFeature;
    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializerProvider;
    import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
    import com.fasterxml.jackson.databind.annotation.JsonSerialize;
    import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
    import com.fasterxml.jackson.databind.ser.std.StdSerializer;
    
    import java.io.IOException;
    import java.util.Map;
    
    public final class Jackson {
    
      private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
          .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
    
      public static void main(String[] args) throws IOException {
        Dto source = makeDto("Master", 31337);
        Dto dst = makeDto("Slave", 0xDEADBEEF);
    
        //1. read value of field "fieldName" from json source
        //2. clones destination object, sets up field "fieldName" and returns it
        //3. in case of no field either on "src" or "dst" - throws an exception
        Object result = restoreValue(dst, "details", OBJECT_MAPPER.writeValueAsString(source));
        System.out.println(result);
      }
    
      private static Object restoreValue(Object targetObject, String fieldName, String sourceObjectAsJson) throws IOException {
        String targetObjectAsJson = OBJECT_MAPPER.writeValueAsString(targetObject);
        Map sourceAsMap = OBJECT_MAPPER.readValue(sourceObjectAsJson, Map.class);
        Map targetAsMap = OBJECT_MAPPER.readValue(targetObjectAsJson, Map.class);
        targetAsMap.put(fieldName, sourceAsMap.get(fieldName));
        String updatedTargetAsJson = OBJECT_MAPPER.writeValueAsString(targetAsMap);
        return OBJECT_MAPPER.readValue(updatedTargetAsJson, targetObject.getClass());
      }
    
      private static Dto makeDto(String name, int magic) {
        Dto dto = new Dto();
        dto.setName(name);
        CustomDetails details = new CustomDetails();
        details.setMagic(magic);
        dto.setDetails(details);
        return dto;
      }
    
      private static final class Dto {
        private String name;
        @JsonSerialize(using = CustomDetails.CustomDetailsSerializer.class)
        @JsonDeserialize(using = CustomDetails.CustomDetailsDeserializer.class)
        private CustomDetails details;
    
        public String getName() {
          return name;
        }
    
        public void setName(String name) {
          this.name = name;
        }
    
        public CustomDetails getDetails() {
          return details;
        }
    
        public void setDetails(CustomDetails details) {
          this.details = details;
        }
    
        @Override
        public String toString() {
          return "Dto{" +
              "name='" + name + '\'' +
              ", details=" + details +
              '}';
        }
      }
    
    
      private static final class CustomDetails {
        private int magic;
    
        public int getMagic() {
          return magic;
        }
    
        public void setMagic(int magic) {
          this.magic = magic;
        }
    
        @Override
        public String toString() {
          return "CustomDetails{" +
              "magic=" + magic +
              '}';
        }
    
        public static final class CustomDetailsSerializer extends StdSerializer<CustomDetails> {
    
          public CustomDetailsSerializer() {
            this(null);
          }
    
    
          public CustomDetailsSerializer(Class<CustomDetails> t) {
            super(t);
          }
    
          @Override
          public void serialize(CustomDetails details, JsonGenerator jg, SerializerProvider serializerProvider) throws IOException {
            jg.writeStartObject();
            jg.writeNumberField("_custom_property_magic", details.magic);
            jg.writeEndObject();
          }
        }
    
    
        private static final class CustomDetailsDeserializer extends StdDeserializer<CustomDetails> {
    
          public CustomDetailsDeserializer() {
            this(null);
          }
    
    
          public CustomDetailsDeserializer(Class<CustomDetails> t) {
            super(t);
          }
    
          @Override
          public CustomDetails deserialize(JsonParser jp, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
            JsonNode node = jp.getCodec().readTree(jp);
            int magic = (Integer) node.get("_custom_property_magic").numberValue();
            CustomDetails
                customDetails = new CustomDetails();
            customDetails.setMagic(magic);
            return customDetails;
          }
        }
      }
    }
    

    所以输出是:

    Dto{name='Slave', details=CustomDetails{magic=31337}}
    

    【讨论】:

    • 感谢您的回答。这当然是一个可能有效的“蛮力”解决方案,但我认为它与我提出的解决方案相比没有任何优势。它实际上看起来只是引入了更多的开销和复杂性。有什么优势吗?
    • 对不起,我似乎错过了你的问题的一部分。因此,您正在寻找支持自定义序列化的字段的类型安全性?请您详细说明一下
    • @Felk 关于优势:对不与 Jackson API 冲突的序列化/反序列化进行更精确的控制。因此,您可以在源对象和目标对象上添加 Jackson 注释,您可以轻松引入类型安全
    • 关于开销:由于您已经在使用自定义序列化,我想编写更高效的序列化程序不是问题,它不会写入比所需更多的数据。所以这是优化的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-27
    • 2016-02-12
    • 1970-01-01
    • 2018-03-06
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多