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

时间:2018-09-20 15:08:53

标签: java serialization jackson

我需要编写一个方法,该方法接受一些对象,给定对象的类中存在的某些字段名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);
}

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

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

2 个答案:

答案 0 :(得分: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类的其他方面会影响单个字段的反序列化过程

答案 1 :(得分:0)

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