Десериализовать строку, используя пользовательский десериализатор, указанный в поле класса - PullRequest
0 голосов
/ 20 сентября 2018

Мне нужно написать метод, который принимает некоторый объект, некоторое имя поля 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) сериализатором:

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

Одним из возможных решений будет ручная проверка на JsonDeserialize аннотаций.Тем не менее, я действительно не хочу пытаться копировать любые политики, которые Джексон использует, чтобы решить, какой сериализатор использовать, поскольку это кажется хрупким (например, глобально зарегистрированные сериализаторы).

Есть ли хороший способ десериализации значения с использованиемконфигурация десериализации поля, определенная в классе DTO?Может быть, десериализовать значение в тип поля, передавая аннотации поля Джексону, чтобы они удостоились чести?

Мне удалось получить экземпляр AnnotatedMember, который содержит всю необходимую информацию (JSON-аннотации и отражающее поле (или доступ к установщику / получателю), но не смог понять, как я мог бы использовать его для десериализации отдельного значения из-за отсутствия документации:

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 голосов
/ 01 октября 2018
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}}
0 голосов
/ 20 сентября 2018

Одной из возможностей будет сериализация объекта, замена заданного поля и последующая десериализация.Это легко сделать при сериализации от / до 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 влияют на процесс десериализации одного поля

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...