TreeTraversingParserParentContext1803Test.java

package tools.jackson.databind.node;

import org.junit.jupiter.api.Test;

import tools.jackson.core.*;
import tools.jackson.databind.*;
import tools.jackson.databind.testutil.DatabindTestUtil;

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

// [databind#1803]: TreeTraversingParser should allow passing of parent context
public class TreeTraversingParserParentContext1803Test
    extends DatabindTestUtil
{
    private final ObjectMapper MAPPER = newJsonMapper();

    // Without a parent context, traversal of a subtree reports paths relative
    // to that subtree (existing behavior).
    @Test
    void noParentContextByDefault() throws Exception
    {
        JsonNode root = MAPPER.readTree("{\"wrapped\":{\"x\":1}}");
        JsonNode sub = root.get("wrapped");

        try (TreeTraversingParser p = new TreeTraversingParser(sub)) {
            assertNull(p.streamReadContext().getParent());
            assertToken(JsonToken.START_OBJECT, p.nextToken());
            assertToken(JsonToken.PROPERTY_NAME, p.nextToken()); // "x"
            assertEquals("/x", p.streamReadContext().pathAsPointer().toString());
        }
    }

    // Attaching a parent context (here: the context of an outer parser positioned
    // at the "wrapped" property) makes the subtree traversal report the full path.
    @Test
    void parentContextViaConstructor() throws Exception
    {
        JsonNode root = MAPPER.readTree("{\"wrapped\":{\"x\":1}}");
        JsonNode sub = root.get("wrapped");

        try (TreeTraversingParser outer = new TreeTraversingParser(root)) {
            assertToken(JsonToken.START_OBJECT, outer.nextToken());
            assertToken(JsonToken.PROPERTY_NAME, outer.nextToken()); // "wrapped"
            assertEquals("wrapped", outer.currentName());
            TokenStreamContext parent = outer.streamReadContext();
            assertEquals("/wrapped", parent.pathAsPointer().toString());

            try (TreeTraversingParser inner = new TreeTraversingParser(sub,
                    ObjectReadContext.empty(), parent)) {
                assertSame(parent, inner.streamReadContext().getParent());
                assertToken(JsonToken.START_OBJECT, inner.nextToken());
                assertToken(JsonToken.PROPERTY_NAME, inner.nextToken()); // "x"
                assertEquals("/wrapped/x",
                        inner.streamReadContext().pathAsPointer().toString());
            }
        }
    }

    // Same effect via the post-construction override setter.
    @Test
    void parentContextViaOverride() throws Exception
    {
        JsonNode root = MAPPER.readTree("{\"wrapped\":{\"x\":1}}");
        JsonNode sub = root.get("wrapped");

        try (TreeTraversingParser outer = new TreeTraversingParser(root)) {
            assertToken(JsonToken.START_OBJECT, outer.nextToken());
            assertToken(JsonToken.PROPERTY_NAME, outer.nextToken()); // "wrapped"
            TokenStreamContext parent = outer.streamReadContext();

            try (TreeTraversingParser inner = new TreeTraversingParser(sub)
                    .overrideParentContext(parent)) {
                assertSame(parent, inner.streamReadContext().getParent());
                assertToken(JsonToken.START_OBJECT, inner.nextToken());
                assertToken(JsonToken.PROPERTY_NAME, inner.nextToken()); // "x"
                assertEquals("/wrapped/x",
                        inner.streamReadContext().pathAsPointer().toString());
            }
        }
    }

    // Attaching a parent context also exposes the parent's "current value" via
    // the parent-context chain, which is the other half of what #1803 asks for.
    @Test
    void parentCurrentValueVisibleThroughChain() throws Exception
    {
        JsonNode root = MAPPER.readTree("{\"wrapped\":{\"x\":1}}");
        JsonNode sub = root.get("wrapped");
        Object marker = new Object();

        try (TreeTraversingParser outer = new TreeTraversingParser(root)) {
            assertToken(JsonToken.START_OBJECT, outer.nextToken());
            assertToken(JsonToken.PROPERTY_NAME, outer.nextToken()); // "wrapped"
            outer.assignCurrentValue(marker);
            TokenStreamContext parent = outer.streamReadContext();

            try (TreeTraversingParser inner = new TreeTraversingParser(sub)
                    .overrideParentContext(parent)) {
                assertToken(JsonToken.START_OBJECT, inner.nextToken());
                assertToken(JsonToken.PROPERTY_NAME, inner.nextToken()); // "x"
                // Inner root has no value of its own...
                assertNull(inner.streamReadContext().getParent().currentValue());
                // ...but walking up to the attached parent reaches the marker.
                assertSame(marker,
                        inner.streamReadContext().getParent().getParent().currentValue());
            }
        }
    }

    // Attaching a parent context must not disturb normal traversal of nested
    // structure within the subtree itself.
    @Test
    void nestedTraversalUnaffectedByParentContext() throws Exception
    {
        JsonNode root = MAPPER.readTree("{\"wrapped\":{\"a\":[1,2],\"b\":3}}");
        JsonNode sub = root.get("wrapped");

        try (TreeTraversingParser outer = new TreeTraversingParser(root)) {
            outer.nextToken(); // START_OBJECT
            outer.nextToken(); // PROPERTY_NAME "wrapped"
            TokenStreamContext parent = outer.streamReadContext();

            try (TreeTraversingParser inner = new TreeTraversingParser(sub)
                    .overrideParentContext(parent)) {
                assertToken(JsonToken.START_OBJECT, inner.nextToken());
                assertToken(JsonToken.PROPERTY_NAME, inner.nextToken()); // "a"
                assertToken(JsonToken.START_ARRAY, inner.nextToken());
                assertToken(JsonToken.VALUE_NUMBER_INT, inner.nextToken()); // 1
                assertEquals("/wrapped/a/0",
                        inner.streamReadContext().pathAsPointer().toString());
                assertToken(JsonToken.VALUE_NUMBER_INT, inner.nextToken()); // 2
                assertToken(JsonToken.END_ARRAY, inner.nextToken());
                assertToken(JsonToken.PROPERTY_NAME, inner.nextToken()); // "b"
                assertEquals("/wrapped/b",
                        inner.streamReadContext().pathAsPointer().toString());
                assertToken(JsonToken.VALUE_NUMBER_INT, inner.nextToken()); // 3
                assertToken(JsonToken.END_OBJECT, inner.nextToken());
                assertNull(inner.nextToken());
            }
        }
    }
}