Десериализация JSON объекта двунаправленного типа "один ко многим" с помощью Jackson ObjectMapper - PullRequest
0 голосов
/ 10 июля 2020

Я пытаюсь десериализовать объект JSON, например

{ 
  "name":"aaa",
  "children": [ 
    {"name":"bbb"}
  ]
}

, в объекты Java, в которых дочерний элемент имеет ссылку на родительский объект, например:

public class Parent {
  public String name;
  public List<Child> children;
}
public class Child {
  public String name;
  public Parent parent;
}

// ...
new ObjectMapper().readValue(<JSON>, Parent.class);

При такой десериализации Child#parent не будет указывать на родительский объект. Я читал о двух подходах во время своего онлайн-исследования, но, похоже, они не работают.

1. Добавление аргумента конструктора в класс Child для установки родительского объекта

public class Child {
  public String name;
  public Parent parent;
  public Child(Parent parent) {
    this.parent = parent;
  }
}

При этом я получаю сообщение об ошибке:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
  Cannot construct instance of `Child` (no Creators, like default construct, exist): 
  cannot deserialize from Object value (no delegate- or property-based Creator)
  at [Source: (String)"{"name":"aaa","children":[{"name":"bbb"}]}"; line: 1, column: 27] 
  (through reference chain: Parent["children"]->java.util.ArrayList[0])

2. Использование аннотаций @JsonBackReference и @JsonManagedReference

public class Parent {
  public String name;
  @JsonBackReference
  public List<Child> children;
}
public class Child {
  public String name;
  @JsonManagedReference
  public Parent parent;
}

Это не работает с:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
  Cannot handle managed/back reference 'defaultReference':
  back reference type (java.util.List) not compatible with managed type (Child)
  at [Source: (String)"{"name":"aaa","children":[{"name":"bbb"}]}"; line: 1, column: 1]

JavaDo c из @JsonBackReference говорит, что его нельзя применить к коллекций, поэтому он, очевидно, не работает, но мне интересно, почему в Интернете так много примеров, где он применяется к коллекции.

Вопрос Как я могу добиться, чтобы дочерний объект получил своего родителя / владелец объекта автоматически устанавливается при десериализации графа объекта. На самом деле я бы предпочел каким-то образом заставить работать первый подход, так как он не загрязняет, требуется загрязнять классы аннотациями c фреймворка.

Ответы [ 2 ]

0 голосов
/ 13 июля 2020

Мне потребовалось еще немного времени, чтобы покопаться в исходном коде Джексона. Я придумал обобщенное решение с использованием пользовательского BeanDeserializer. Пользовательский десериализатор проверяет, имеет ли соответствующий класс Java для сериализуемого узла JSON конструктор с одним аргументом, который может принимать родительский объект и использовать его для создания экземпляра объекта.

import org.apache.commons.lang3.reflect.ConstructorUtils;

public static class CustomBeanDeserializer extends BeanDeserializer {
  private static final long serialVersionUID = 1L;

  public CustomBeanDeserializer(BeanDeserializerBase src) {
    super(src);
  }

  @Override
  protected Object deserializeFromObjectUsingNonDefault(JsonParser p, DeserializationContext ctxt) throws IOException {
    Object parentObject = getParentObject(p);
    if (parentObject != null) {
      // determine constructor that takes parent object
      Constructor<?> ctor = ConstructorUtils.getMatchingAccessibleConstructor(_beanType.getRawClass(), parentObject.getClass());
      if (ctor != null) {
        try {
          // instantiate object
          Object bean = ctor.newInstance(parentObject);
          p.setCurrentValue(bean);
          // deserialize fields
          if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
            String propName = p.getCurrentName();
            do {
              p.nextToken();
              SettableBeanProperty prop = _beanProperties.find(propName);
              if (prop == null) {
                handleUnknownVanilla(p, ctxt, bean, propName);
                continue;
              }
              try {
                prop.deserializeAndSet(p, ctxt, bean);
              } catch (final Exception e) {
                wrapAndThrow(e, bean, propName, ctxt);
              }
            } while ((propName = p.nextFieldName()) != null);
          }
          return bean;
        } catch (ReflectiveOperationException ex) {
          ex.printStackTrace();
        }
      }
    }
    return super.deserializeFromObjectUsingNonDefault(p, ctxt);
  }

  private Object getParentObject(JsonParser p) {
    JsonStreamContext parentCtx = p.getParsingContext().getParent();
    if (parentCtx == null)
      return null;

    Object parentObject = parentCtx.getCurrentValue();
    if (parentObject == null)
      return null;

    if (parentObject instanceof Collection || parentObject instanceof Map || parentObject.getClass().isArray()) {
      parentCtx = parentCtx.getParent();
      if (parentCtx != null) {
        parentObject = parentCtx.getCurrentValue();
      }
    }
    return parentObject;
  }
}

десериализатор можно использовать следующим образом:

ObjectMapper objectMapper = new ObjectMapper();
SimpleModule myModule = new SimpleModule();
myModule.setDeserializerModifier(new BeanDeserializerModifier() {
  @Override
  public JsonDeserializer<?> modifyDeserializer(DeserializationConfig cfg, BeanDescription beanDescr, JsonDeserializer<?> deserializer) {
    if (deserializer instanceof BeanDeserializerBase)
      return new CustomBeanDeserializer((BeanDeserializerBase) deserializer);
    return deserializer;
  }
});
objectMapper.registerModule(myModule);

objectMapper.readValue(<JSON>, Parent.class);

Меня все еще интересует лучшее решение, требующее меньшего количества настраиваемого кода.

0 голосов
/ 11 июля 2020

Для этого вам, вероятно, потребуется написать свой собственный простой десериализатор. Смутно может выглядеть так:

public class FamilyDeserializer extends JsonDeserializer<Parent> {

private ObjectMapper mapper;

@Override
public Parent deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
    JsonNode node = p.getCodec().readTree(p);

    Parent parent = mapper.readValue(p, new TypeReference<Parent>() {
    });

    Child child = mapper.readValue(node.get("children").toString(), new TypeReference<Child>() {
    });

    parent.setChild(child);
    return parent;
}
...