JsonIdentityInfoAndBackReferences3964Test.java

package com.fasterxml.jackson.databind.tofix;

import java.util.List;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.*;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
import com.fasterxml.jackson.databind.testutil.failure.JacksonTestFailureExpected;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

// [databind#3964] MismatchedInputException, Bean not yet resolved
class JsonIdentityInfoAndBackReferences3964Test extends DatabindTestUtil {
    /**
     * Fails : Original test
     */
    @JsonIdentityInfo(
            generator = ObjectIdGenerators.PropertyGenerator.class,
            property = "id",
            scope = Tree.class
    )
    public static class Tree {
        protected final int id;
        protected List<Fruit> fruits;

        @JsonCreator
        public Tree(@JsonProperty("id") int id, @JsonProperty("fruits") List<Fruit> fruits) {
            this.id = id;
            this.fruits = fruits;
        }

        public int getId() {
            return id;
        }

        public List<Fruit> getFruits() {
            return fruits;
        }

        public void setFruits(List<Fruit> fruits) {
            this.fruits = fruits;
        }
    }

    @JsonIdentityInfo(
            generator = ObjectIdGenerators.PropertyGenerator.class,
            property = "id",
            scope = Fruit.class
    )
    public static class Fruit {
        protected final int id;
        protected List<Calories> calories;

        @JsonBackReference("id")
        protected Tree tree;

        @JsonCreator
        public Fruit(@JsonProperty("id") int id, @JsonProperty("calories") List<Calories> calories) {
            this.id = id;
            this.calories = calories;
        }

        public int getId() {
            return id;
        }

        public Tree getTree() {
            return tree;
        }

        public void setTree(Tree tree) {
            this.tree = tree;
        }

        public List<Calories> getCalories() {
            return calories;
        }

        public void setCalories(List<Calories> calories) {
            this.calories = calories;
        }
    }

    @JsonIdentityInfo(
            generator = ObjectIdGenerators.PropertyGenerator.class,
            property = "id",
            scope = Calories.class
    )
    public static class Calories {
        protected final int id;
        protected Fruit fruit;

        @JsonCreator
        public Calories(@JsonProperty("id") int id) {
            this.id = id;
        }

        public int getId() {
            return id;
        }

        public Fruit getFruit() {
            return fruit;
        }

        public void setFruit(Fruit fruit) {
            this.fruit = fruit;
        }
    }

    /**
     * Fails : Lean version that fails and Without getters and setters
     */
    @JsonIdentityInfo(
            generator = ObjectIdGenerators.PropertyGenerator.class,
            property = "id",
            scope = Animal.class
    )
    public static class Animal {
        public final int id;
        public List<Cat> cats;

        @JsonCreator
        public Animal(@JsonProperty("id") int id, @JsonProperty("cats") List<Cat> cats) {
            this.id = id;
            this.cats = cats;
        }
    }

    @JsonIdentityInfo(
            generator = ObjectIdGenerators.PropertyGenerator.class,
            property = "id",
            scope = Cat.class
    )
    public static class Cat {
        public int id;
        public List<Food> foods;
        @JsonBackReference("id")
        public Animal animal;

        @JsonCreator
        public Cat(@JsonProperty("id") int id, @JsonProperty("foods") List<Food> foods) {
            this.id = id;
            this.foods = foods;
        }
    }

    @JsonIdentityInfo(
            generator = ObjectIdGenerators.PropertyGenerator.class,
            property = "id",
            scope = Food.class
    )
    public static class Food {
        public int id;
        public Cat cat;

        @JsonCreator
        public Food(@JsonProperty("id") int id) {
            this.id = id;
        }
    }

    /**
     * Passes : Testing lean without getters and setters
     * and also without {@link JsonCreator}.
     */
    @JsonIdentityInfo(
            generator = ObjectIdGenerators.PropertyGenerator.class,
            property = "id",
            scope = Fish.class
    )
    public static class Fish {
        public int id;
        public List<Squid> squids;
    }

    @JsonIdentityInfo(
            generator = ObjectIdGenerators.PropertyGenerator.class,
            property = "id",
            scope = Squid.class
    )
    public static class Squid {
        public int id;
        public List<Shrimp> shrimps;
        @JsonBackReference("id")
        public Fish fish;
    }

    @JsonIdentityInfo(
            generator = ObjectIdGenerators.PropertyGenerator.class,
            property = "id",
            scope = Shrimp.class
    )
    public static class Shrimp {
        public int id;
        public Squid squid;
    }

    final ObjectMapper MAPPER = jsonMapperBuilder()
            .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).build();

    /**
     * Fails : Original test
     */
    @JacksonTestFailureExpected
    @Test
    void original() throws Exception {
        String json = "{" +
                "              \"id\": 1,\n" +
                "              \"fruits\": [\n" +
                "                {\n" +
                "                  \"id\": 2,\n" +
                "                  \"tree\": 1,\n" +
                "                  \"calories\": [\n" +
                "                    {\n" +
                "                      \"id\": 3,\n" +
                "                      \"fruit\": 2\n" +
                "                    }\n" +
                "                  ]\n" +
                "                }\n" +
                "              ]\n" +
                "            }";

        try {
            Tree tree = MAPPER.readValue(json, Tree.class);
            // should reach here and pass... but throws Exception and fails
            assertEquals(tree, tree.fruits.get(0).tree);
        } catch (MismatchedInputException e) {
            verifyException(e, "Cannot resolve ObjectId forward reference using property 'animal'");
            verifyException(e, "Bean not yet resolved");
            fail("Should not reach");
        }
    }

    /**
     * Fails : Lean version that fails and Without getters and setters
     */
    @JacksonTestFailureExpected
    @Test
    void leanWithoutGetterAndSetters() throws Exception {
        String json = a2q("{" +
                "              'id': 1," +
                "              'cats': [" +
                "                {" +
                "                  'id': 2," +
                "                  'animal': 1," + // reference here
                "                  'foods': [" +
                "                    {" +
                "                      'id': 3," +
                "                      'cat': 2" +
                "                    }" +
                "                  ]" +
                "                }" +
                "              ]" +
                "            }");

        try {
            Animal animal = MAPPER.readValue(json, Animal.class);
            // should reach here and pass... but throws Exception and fails
            assertEquals(animal, animal.cats.get(0).animal);
        } catch (MismatchedInputException e) {
            verifyException(e, "Cannot resolve ObjectId forward reference using property 'animal'");
            verifyException(e, "Bean not yet resolved");
            fail("Should not reach");
        }
    }

    /**
     * Passes : Testing lean without getters and setters
     * and also without {@link JsonCreator}.
     */
    @Test
    void leanWithoutGetterAndSettersAndCreator() throws Exception {
        String json = a2q("{" +
                "              'id': 1," +
                "              'squids': [" +
                "                {" +
                "                  'id': 2," +
                "                  'fish': 1," + // back reference
                "                  'shrimps': [" +
                "                    {" +
                "                      'id': 3," +
                "                      'squid': 2" +
                "                    }" +
                "                  ]" +
                "                }" +
                "              ]" +
                "            }");

        Fish fish = MAPPER.readValue(json, Fish.class);
        assertEquals(fish, fish.squids.get(0).fish);
    }
}