NodeContext2049Test.java

package tools.jackson.databind.node;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.junit.jupiter.api.Test;

import tools.jackson.core.*;
import tools.jackson.databind.*;
import tools.jackson.databind.deser.*;
import tools.jackson.databind.deser.jdk.CollectionDeserializer;
import tools.jackson.databind.deser.std.DelegatingDeserializer;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.testutil.DatabindTestUtil;
import tools.jackson.databind.type.CollectionLikeType;


import static org.junit.jupiter.api.Assertions.assertSame;

/**
 * Test for ensuring that parent references are correctly set during
 * deserialization when using custom deserializers with token stream context.
 */
public class NodeContext2049Test extends DatabindTestUtil
{
    public interface HasParent {
        void setParent(Parent parent);
        Parent getParent();
    }

    static class Child implements HasParent {
        public Parent parent;
        public String property;

        @Override
        public void setParent(Parent p) { parent = p; }
        @Override
        public Parent getParent() { return parent; }
    }

    static class Parent {
        public List<Child> children;
        public Child singleChild;
    }

    static class ListValueInstantiator extends ValueInstantiator {
        @Override
        public String getValueTypeDesc() {
             return List.class.getName();
        }

        @Override
        public Object createUsingDefault(DeserializationContext ctxt) throws JacksonException {
             return new ArrayList<>();
        }

        @Override
        public ValueInstantiator createContextual(DeserializationContext ctxt,
                BeanDescription.Supplier beanDescRef) {
            return this;
        }

        @Override
        public Class<?> getValueClass() {
            return List.class;
        }
    }

    static class ParentSettingDeserializerModifier extends ValueDeserializerModifier {
        private static final long serialVersionUID = 1L;

        @Override
        public BeanDeserializerBuilder updateBuilder(DeserializationConfig config,
                BeanDescription.Supplier beanDescRef, BeanDeserializerBuilder builder) {
             for (Iterator<SettableBeanProperty> propertyIt = builder.getProperties(); propertyIt.hasNext(); ) {
                  SettableBeanProperty property = propertyIt.next();
                  builder.addOrReplaceProperty(property.withValueDeserializer(new ParentSettingDeserializerContextual()), false);
             }
             return builder;
        }
    }

    static class ParentSettingDeserializer extends DelegatingDeserializer {
        public ParentSettingDeserializer(ValueDeserializer<?> delegatee) {
             super(delegatee);
        }

        @Override
        public Object deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
             Object retValue = super.deserialize(p, ctxt);
             if (retValue instanceof HasParent) {
                  HasParent obj = (HasParent) retValue;
                  Parent parent = null;
                  TokenStreamContext parsingContext = p.streamReadContext();
                  while (parent == null && parsingContext != null) {
                       Object currentValue = parsingContext.currentValue();
                       if (currentValue != null && currentValue instanceof Parent) {
                            parent = (Parent) currentValue;
                       }
                       parsingContext = parsingContext.getParent();
                  }
                  if (parent != null) {
                       obj.setParent(parent);
                  }
             }
             return retValue;
        }

        @Override
        protected ValueDeserializer<?> newDelegatingInstance(ValueDeserializer<?> newDelegatee) {
             return new ParentSettingDeserializer(newDelegatee);
        }

   }

    static class ParentSettingDeserializerContextual extends ValueDeserializer<Object> {

        @Override
        public ValueDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
            throws JacksonException
        {
             JavaType propertyType = property.getType();
             JavaType contentType = propertyType;
             if (propertyType.isCollectionLikeType()) {
                  contentType = propertyType.getContentType();
             }
             ValueDeserializer<Object> delegatee = ctxt.findNonContextualValueDeserializer(contentType);
             ValueDeserializer<Object> objectDeserializer = new ParentSettingDeserializer(delegatee);
             ValueDeserializer<?> retValue;
             if (propertyType.isCollectionLikeType()) {
                  CollectionLikeType collectionType = ctxt.getTypeFactory().constructCollectionLikeType(propertyType.getRawClass(),
                            contentType);
                  ValueInstantiator instantiator = new ListValueInstantiator();
                  CollectionDeserializer collDeser = new CollectionDeserializer(collectionType, objectDeserializer, null, instantiator, null);
                  // Need to make the CollectionDeserializer contextual
                  retValue = collDeser.createContextual(ctxt, property);
             } else {
                  retValue = objectDeserializer;
             }
             return retValue;
        }

        @Override
        public Object deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
            throw new UnsupportedOperationException();
        }
    }

    /*
    /**********************************************************************
    /* Test methods
    /**********************************************************************
     */

    private ObjectMapper objectMapper = JsonMapper.builder()
                .addModule(new tools.jackson.databind.JacksonModule() {
              @Override
              public String getModuleName() {
                   return "parentSetting";
              }
              @Override
              public Version version() {
                   return Version.unknownVersion();
              }
              @Override
              public void setupModule(SetupContext context) {
                   context.addDeserializerModifier(new ParentSettingDeserializerModifier());
              }
         })
        .build();

    final static String JSON = "{\n" +
            "     \"children\": [\n" +
            "          {\n" +
            "               \"property\": \"value1\"\n" +
            "          },\n" +
            "          {\n" +
            "               \"property\": \"value2\"\n" +
            "          }\n" +
            "     ],\n" +
            "     \"singleChild\": {\n" +
            "          \"property\": \"value3\"\n" +
            "     }\n" +
            "}";

    @Test
    public void testReadNoBuffering() throws Exception {
        Parent obj = objectMapper.readerFor(Parent.class).readValue(JSON);
        assertSame(obj, obj.singleChild.getParent());
        for (Child child : obj.children) {
            assertSame(obj, child.getParent());
        }
    }

    @Test
    public void testReadFromTree() throws Exception {
        JsonNode tree = objectMapper.readTree(JSON);
        Parent obj = objectMapper.reader().forType(Parent.class).readValue(tree);
        assertSame(obj, obj.singleChild.getParent());
        for (Child child : obj.children) {
            assertSame(obj, child.getParent());
        }
    }
}