Java 使用类的字段中指定的自定义反序列化器反序列化字符串

Java 使用类的字段中指定的自定义反序列化器反序列化字符串,java,serialization,jackson,Java,Serialization,Jackson,我需要编写一个方法,该方法接受给定对象类中存在的一些对象、一些字段名fieldName,以及一些字段值value。该值是字段的JSON序列化形式。该方法将获取值并相应地反序列化,如下所示: static void setField(Object obj, String fieldName, String value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName) Object

我需要编写一个方法,该方法接受给定对象类中存在的一些对象、一些字段名
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);
}
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());
}
(实际上,我只需要检索反序列化的值,而不需要再次设置它,但这使它成为一个更好的示例。) 只要jackson的默认反序列化足够,这就行了。现在,假设我有一个带有自定义(反)序列化器的类:

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?
}

一种可能是序列化对象,替换给定字段,然后再次反序列化它。当从/to
JsonNode
而不是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);
}
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类的其他方面会影响单个字段的反序列化过程。这当然是一个“蛮力”的解决方案,可能有效,但我不认为它比我提出的解决方案有任何优势。实际上,它看起来只是增加了更多的开销和复杂性。有什么好处吗?对不起,我好像漏掉了你问题的一部分。所以您正在为支持自定义序列化的字段寻找类型安全性?你能再详细一点吗please@Felk优点:对序列化/反序列化进行更精确的控制,这与Jackson API不冲突。因此,您可以在源对象和目标对象上添加Jackson注释,您可以很容易地引入类型安全关于开销:因为您已经在使用自定义序列化,我想编写更高效的序列化程序并不是问题,因为它不会写入比所需更多的数据。所以这是一个优化的问题。你能提供你想要实现的JSON输出示例吗?如果没有输出部分,理解起来似乎有点复杂
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;
      }
    }
  }
}