POJOAsArrayTest.java

package tools.jackson.databind.struct;

import java.util.*;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;

import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JsonDeserialize;
import tools.jackson.databind.cfg.MapperConfig;
import tools.jackson.databind.exc.InvalidDefinitionException;
import tools.jackson.databind.exc.MismatchedInputException;
import tools.jackson.databind.introspect.Annotated;
import tools.jackson.databind.introspect.JacksonAnnotationIntrospector;
import tools.jackson.databind.testutil.DatabindTestUtil;

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

public class POJOAsArrayTest extends DatabindTestUtil
{
    // // // Inner types for basic POJO-as-Array tests

    static class PojoAsArrayWrapper
    {
        @JsonFormat(shape=JsonFormat.Shape.ARRAY)
        public PojoAsArray value;

        public PojoAsArrayWrapper() { }
        protected PojoAsArrayWrapper(String name, int x, int y, boolean c) {
            value = new PojoAsArray(name, x, y, c);
        }
    }

    @JsonPropertyOrder(alphabetic=true)
    static class NonAnnotatedXY {
        public int x, y;

        public NonAnnotatedXY() { }
        protected NonAnnotatedXY(int x0, int y0) {
            x = x0;
            y = y0;
        }
    }

    // note: must be serialized/deserialized alphabetically; fields NOT declared in that order
    @JsonPropertyOrder(alphabetic=true)
    static class PojoAsArray
    {
        public int x, y;
        public String name;
        public boolean complete;

        public PojoAsArray() { }
        protected PojoAsArray(String name, int x, int y, boolean c) {
            this.name = name;
            this.x = x;
            this.y = y;
            this.complete = c;
        }
    }

    @JsonPropertyOrder(alphabetic=true)
    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    static class FlatPojo
    {
        public int x, y;
        public String name;
        public boolean complete;

        public FlatPojo() { }
        protected FlatPojo(String name, int x, int y, boolean c) {
            this.name = name;
            this.x = x;
            this.y = y;
            this.complete = c;
        }
    }

    static class ForceArraysIntrospector extends JacksonAnnotationIntrospector
    {
        private static final long serialVersionUID = 1L;

        @Override
        public JsonFormat.Value findFormat(MapperConfig<?> config, Annotated a) {
            return new JsonFormat.Value().withShape(JsonFormat.Shape.ARRAY);
        }
    }

    static class A {
        public B value = new B();
    }

    @JsonPropertyOrder(alphabetic=true)
    static class B {
        public int x = 1;
        public int y = 2;
    }

    @JsonFormat(shape=Shape.ARRAY)
    static class SingleBean {
        public String name = "foo";
    }

    @JsonPropertyOrder(alphabetic=true)
    @JsonFormat(shape=Shape.ARRAY)
    static class TwoStringsBean {
        public String bar = null;
        public String foo = "bar";
    }

    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    @JsonPropertyOrder(alphabetic=true)
    static class AsArrayWithMap
    {
        public Map<Integer,Integer> attrs;

        public AsArrayWithMap() { }
        protected AsArrayWithMap(int x, int y) {
            attrs = new HashMap<Integer,Integer>();
            attrs.put(x, y);
        }
    }

    // [databind#2077]
    @JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.WRAPPER_ARRAY)
    @JsonSubTypes({
        @JsonSubTypes.Type(value = DirectLayout.class, name = "Direct"),
    })
    public interface Layout {
    }

    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    public static class DirectLayout implements Layout {
    }

    // [databind#4961]
    static class WrapperForAnyGetter {
        public BeanWithAnyGetter value;
    }

    @JsonFormat(shape = JsonFormat.Shape.ARRAY)
    @JsonPropertyOrder({ "firstProperty", "secondProperties", "forthProperty" })
    static class BeanWithAnyGetter {
        public String firstProperty = "first";
        public String secondProperties = "second";
        public String forthProperty = "forth";
        @JsonAnyGetter
        public Map<String, String> getAnyProperty() {
            Map<String, String> map = new TreeMap<>();
            map.put("third_A", "third_A");
            map.put("third_B", "third_B");
            return map;
        }
    }

    // [databind#646]
    @JsonFormat(shape = JsonFormat.Shape.ARRAY)
    @JsonPropertyOrder(alphabetic = true)
    static class Outer646 {
        protected Map<String, TheItem646> attributes;

        public Outer646() {
            attributes = new HashMap<String, TheItem646>();
        }

        @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_ARRAY)
        public Map<String, TheItem646> getAttributes() {
            return attributes;
        }
    }

    @JsonFormat(shape = JsonFormat.Shape.ARRAY)
    @JsonPropertyOrder(alphabetic = true)
    static class TheItem646 {

        @JsonFormat(shape = JsonFormat.Shape.ARRAY)
        @JsonPropertyOrder(alphabetic = true)
        public static class NestedItem {
            public String nestedStrValue;

            @JsonCreator
            public NestedItem(@JsonProperty("nestedStrValue") String nestedStrValue) {
                this.nestedStrValue = nestedStrValue;
            }
        }

        private String strValue;
        private boolean boolValue;
        private List<NestedItem> nestedItems;

        @JsonCreator
        public TheItem646(@JsonProperty("strValue") String strValue, @JsonProperty("boolValue") boolean boolValue, @JsonProperty("nestedItems") List<NestedItem> nestedItems) {
            this.strValue = strValue;
            this.boolValue = boolValue;
            this.nestedItems = nestedItems;
        }

        public String getStrValue() {
            return strValue;
        }

        public void setStrValue(String strValue) {
            this.strValue = strValue;
        }

        public boolean isBoolValue() {
            return boolValue;
        }

        public void setBoolValue(boolean boolValue) {
            this.boolValue = boolValue;
        }

        public List<NestedItem> getNestedItems() {
            return nestedItems;
        }

        public void setNestedItems(List<NestedItem> nestedItems) {
            this.nestedItems = nestedItems;
        }
    }

    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    static class CreatorWithIndex {
        protected int _a, _b;

        @JsonCreator
        public CreatorWithIndex(@JsonProperty(index=0, value="a") int a,
                @JsonProperty(index=1, value="b") int b) {
            this._a = a;
            this._b = b;
        }
    }

    // // // Inner types for advanced POJO-as-Array tests (views, creators)

    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    @JsonPropertyOrder(alphabetic=true)
    static class CreatorAsArray
    {
        protected int x, y;
        public int a, b;

        @JsonCreator
        public CreatorAsArray(@JsonProperty("x") int x, @JsonProperty("y") int y)
        {
            this.x = x;
            this.y = y;
        }

        public int getX() { return x; }
        public int getY() { return y; }
    }

    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    @JsonPropertyOrder({"a","b","x","y"})
    static class CreatorAsArrayShuffled
    {
        protected int x, y;
        public int a, b;

        @JsonCreator
        public CreatorAsArrayShuffled(@JsonProperty("x") int x, @JsonProperty("y") int y)
        {
            this.x = x;
            this.y = y;
        }

        public int getX() { return x; }
        public int getY() { return y; }
    }

    static class ViewA { }
    static class ViewB { }

    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    @JsonPropertyOrder(alphabetic=true)
    static class AsArrayWithView
    {
        @JsonView(ViewA.class)
        public int a;
        @JsonView(ViewB.class)
        public int b;
        public int c;
    }

    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    @JsonPropertyOrder(alphabetic=true)
    static class AsArrayWithViewAndCreator
    {
        @JsonView(ViewA.class)
        public int a;
        @JsonView(ViewB.class)
        public int b;
        public int c;

        @JsonCreator
        public AsArrayWithViewAndCreator(@JsonProperty("a") int a,
                @JsonProperty("b") int b,
                @JsonProperty("c") int c)
        {
            this.a = a;
            this.b = b;
            this.c = c;
        }
    }

    // // // Inner types for POJO-as-Array with Builder tests

    @JsonDeserialize(builder=SimpleBuilderXY.class)
    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    @JsonPropertyOrder(alphabetic=true)
    static class ValueClassXY
    {
        final int _x, _y;

        protected ValueClassXY(int x, int y) {
            _x = x+1;
            _y = y+1;
        }
    }

    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    @JsonPropertyOrder(alphabetic=true)
    static class SimpleBuilderXY
    {
        public int x, y;

        protected SimpleBuilderXY() { }
        protected SimpleBuilderXY(int x0, int y0) {
            x = x0;
            y = y0;
        }

        public SimpleBuilderXY withX(int x0) {
            this.x = x0;
            return this;
        }

        public SimpleBuilderXY withY(int y0) {
            this.y = y0;
            return this;
        }

        public ValueClassXY build() {
            return new ValueClassXY(x, y);
        }
    }

    @JsonDeserialize(builder=CreatorBuilder.class)
    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    @JsonPropertyOrder(alphabetic=true)
    static class CreatorValue
    {
        final int a, b, c;

        protected CreatorValue(int a, int b, int c) {
            this.a = a;
            this.b = b;
            this.c = c;
        }
    }

    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    static class CreatorBuilder {
        private final int a, b;

        private int c;

        @JsonCreator
        public CreatorBuilder(@JsonProperty("a") int a,
                @JsonProperty("b") int b)
        {
            this.a = a;
            this.b = b;
        }

        @JsonView(String.class)
        public CreatorBuilder withC(int v) {
            c = v;
            return this;
        }

        public CreatorValue build() {
            return new CreatorValue(a, b, c);
        }
    }

    // // // Inner types for roundtrip tests

    @JsonFormat(shape=JsonFormat.Shape.ARRAY)
    @JsonPropertyOrder({"content", "images"})
    static class MediaItemAsArray
    {
        public enum Player { JAVA, FLASH;  }
        public enum Size { SMALL, LARGE; }

        private List<Photo> _photos;
        private Content _content;

        public MediaItemAsArray() { }

        protected MediaItemAsArray(Content c)
        {
            _content = c;
        }

        public void addPhoto(Photo p) {
            if (_photos == null) {
                _photos = new ArrayList<Photo>();
            }
            _photos.add(p);
        }

        public List<Photo> getImages() { return _photos; }
        public void setImages(List<Photo> p) { _photos = p; }

        public Content getContent() { return _content; }
        public void setContent(Content c) { _content = c; }

        @JsonFormat(shape=JsonFormat.Shape.ARRAY)
        @JsonPropertyOrder({"uri","title","width","height","size"})
        static class Photo
        {
            private String _uri;
            private String _title;
            private int _width;
            private int _height;
            private Size _size;

            public Photo() {}
            protected Photo(String uri, String title, int w, int h, Size s)
            {
              _uri = uri;
              _title = title;
              _width = w;
              _height = h;
              _size = s;
            }

          public String getUri() { return _uri; }
          public String getTitle() { return _title; }
          public int getWidth() { return _width; }
          public int getHeight() { return _height; }
          public Size getSize() { return _size; }

          public void setUri(String u) { _uri = u; }
          public void setTitle(String t) { _title = t; }
          public void setWidth(int w) { _width = w; }
          public void setHeight(int h) { _height = h; }
          public void setSize(Size s) { _size = s; }
        }

        @JsonFormat(shape=JsonFormat.Shape.ARRAY)
        @JsonPropertyOrder({"uri","title","width","height","format","duration","size","bitrate","persons","player","copyright"})
        public static class Content
        {
            private Player _player;
            private String _uri;
            private String _title;
            private int _width;
            private int _height;
            private String _format;
            private long _duration;
            private long _size;
            private int _bitrate;
            private List<String> _persons;
            private String _copyright;

            public Content() { }

            public void addPerson(String p) {
                if (_persons == null) {
                    _persons = new ArrayList<>();
                }
                _persons.add(p);
            }

            public Player getPlayer() { return _player; }
            public String getUri() { return _uri; }
            public String getTitle() { return _title; }
            public int getWidth() { return _width; }
            public int getHeight() { return _height; }
            public String getFormat() { return _format; }
            public long getDuration() { return _duration; }
            public long getSize() { return _size; }
            public int getBitrate() { return _bitrate; }
            public List<String> getPersons() { return _persons; }
            public String getCopyright() { return _copyright; }

            public void setPlayer(Player p) { _player = p; }
            public void setUri(String u) {  _uri = u; }
            public void setTitle(String t) {  _title = t; }
            public void setWidth(int w) {  _width = w; }
            public void setHeight(int h) {  _height = h; }
            public void setFormat(String f) {  _format = f;  }
            public void setDuration(long d) {  _duration = d; }
            public void setSize(long s) {  _size = s; }
            public void setBitrate(int b) {  _bitrate = b; }
            public void setPersons(List<String> p) {  _persons = p; }
            public void setCopyright(String c) {  _copyright = c; }
        }
    }

    /*
    /*****************************************************
    /* Mapper instances
    /*****************************************************
     */

    private final static ObjectMapper MAPPER = newJsonMapper();

    // 06-Jan-2025, tatu: NOTE! need to make sure Default View Inclusion
    //   is enabled for view tests to work as expected
    private final static ObjectMapper MAPPER_WITH_VIEWS = jsonMapperBuilder()
            .enable(MapperFeature.DEFAULT_VIEW_INCLUSION)
            .build();

    /*
    /*****************************************************
    /* Basic tests
    /*****************************************************
     */

    /**
     * Test that verifies that property annotation works
     */
    @Test
    public void testReadSimplePropertyValue() throws Exception
    {
        String json = "{\"value\":[true,\"Foobar\",42,13]}";
        PojoAsArrayWrapper p = MAPPER.readValue(json, PojoAsArrayWrapper.class);
        assertNotNull(p.value);
        assertTrue(p.value.complete);
        assertEquals("Foobar", p.value.name);
        assertEquals(42, p.value.x);
        assertEquals(13, p.value.y);
    }

    /**
     * Test that verifies that Class annotation works
     */
    @Test
    public void testReadSimpleRootValue() throws Exception
    {
        String json = "[false,\"Bubba\",1,2]";
        FlatPojo p = MAPPER.readValue(json, FlatPojo.class);
        assertFalse(p.complete);
        assertEquals("Bubba", p.name);
        assertEquals(1, p.x);
        assertEquals(2, p.y);
    }

    /**
     * Test that verifies that property annotation works
     */
    @Test
    public void testWriteSimplePropertyValue() throws Exception
    {
        String json = MAPPER.writeValueAsString(new PojoAsArrayWrapper("Foobar", 42, 13, true));
        // will have wrapper POJO, then POJO-as-array..
        assertEquals("{\"value\":[true,\"Foobar\",42,13]}", json);
    }

    /**
     * Test that verifies that Class annotation works
     */
    @Test
    public void testWriteSimpleRootValue() throws Exception
    {
        String json = MAPPER.writeValueAsString(new FlatPojo("Bubba", 1, 2, false));
        // will have wrapper POJO, then POJO-as-array..
        assertEquals("[false,\"Bubba\",1,2]", json);
    }

    // [Issue#223]
    @Test
    public void testNullColumn() throws Exception
    {
        assertEquals("[null,\"bar\"]", MAPPER.writeValueAsString(new TwoStringsBean()));
    }

    /*
    /*****************************************************
    /* Compatibility with "single-elem as array" feature
    /*****************************************************
     */

    @Test
    public void testSerializeAsArrayWithSingleProperty() throws Exception {
        ObjectMapper mapper = jsonMapperBuilder()
                .enable(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
                .build();
        String json = mapper.writeValueAsString(new SingleBean());
        assertEquals("\"foo\"", json);
    }

    @Test
    public void testBeanAsArrayUnwrapped() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
                .build();
        SingleBean result = mapper.readValue("[\"foobar\"]", SingleBean.class);
        assertNotNull(result);
        assertEquals("foobar", result.name);
    }

    /*
    /*****************************************************
    /* Round-trip tests
    /*****************************************************
     */

    @Test
    public void testAnnotationOverride() throws Exception
    {
        // by default, POJOs become JSON Objects;
        assertEquals("{\"value\":{\"x\":1,\"y\":2}}", MAPPER.writeValueAsString(new A()));

        // but override should change it:
        ObjectMapper mapper2 = jsonMapperBuilder()
                .annotationIntrospector(new ForceArraysIntrospector())
                .build();
        assertEquals("[[1,2]]", mapper2.writeValueAsString(new A()));

        // and allow reading back, too
    }

    @Test
    public void testWithMaps() throws Exception
    {
        AsArrayWithMap input = new AsArrayWithMap(1, 2);
        String json = MAPPER.writeValueAsString(input);
        AsArrayWithMap output = MAPPER.readValue(json, AsArrayWithMap.class);
        assertNotNull(output);
        assertNotNull(output.attrs);
        assertEquals(1, output.attrs.size());
        assertEquals(Integer.valueOf(2), output.attrs.get(1));
    }

    @Test
    public void testSimpleWithIndex() throws Exception
    {
        // as POJO:
//        CreatorWithIndex value = MAPPER.readValue(aposToQuotes("{'b':1,'a':2}"),
        CreatorWithIndex value = MAPPER.readValue(a2q("[2,1]"),
                CreatorWithIndex.class);
        assertEquals(2, value._a);
        assertEquals(1, value._b);
    }

    @Test
    public void testWithConfigOverrides() throws Exception
    {
        ObjectMapper mapper = jsonMapperBuilder()
                .withConfigOverride(NonAnnotatedXY.class,
                        o -> o.setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.ARRAY)))
                .build();
        String json = mapper.writeValueAsString(new NonAnnotatedXY(2, 3));
        assertEquals("[2,3]", json);

        // also, read it back
        NonAnnotatedXY result = mapper.readValue(json, NonAnnotatedXY.class);
        assertNotNull(result);
        assertEquals(3, result.y);
    }

    @Test
    public void testMedaItemRoundtrip() throws Exception
    {
        MediaItemAsArray.Content c = new MediaItemAsArray.Content();
        c.setBitrate(9600);
        c.setCopyright("none");
        c.setDuration(360000L);
        c.setFormat("lzf");
        c.setHeight(640);
        c.setSize(128000L);
        c.setTitle("Amazing Stuff For Something Or Oth\u00CBr!");
        c.setUri("http://multi.fario.us/index.html");
        c.setWidth(1400);

        c.addPerson("Joe Sixp\u00e2ck");
        c.addPerson("Ezekiel");
        c.addPerson("Sponge-Bob Squarepant\u00DF");

        MediaItemAsArray input = new MediaItemAsArray(c);
        input.addPhoto(new MediaItemAsArray.Photo());
        input.addPhoto(new MediaItemAsArray.Photo());
        input.addPhoto(new MediaItemAsArray.Photo());

        String json = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(input);

        MediaItemAsArray output = MAPPER.readValue(new java.io.StringReader(json), MediaItemAsArray.class);
        assertNotNull(output);

        assertNotNull(output.getImages());
        assertEquals(input.getImages().size(), output.getImages().size());
        assertNotNull(output.getContent());
        assertEquals(input.getContent().getTitle(), output.getContent().getTitle());
        assertEquals(input.getContent().getUri(), output.getContent().getUri());

        // compare re-serialization as a simple check as well
        assertEquals(json, MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(output));
    }

    /*
    /*****************************************************
    /* Tests with views and creators
    /*****************************************************
     */

    @Test
    public void testWithView() throws Exception
    {
        // Ok, first, ensure that serializer will "black out" filtered properties
        AsArrayWithView input = new AsArrayWithView();
        input.a = 1;
        input.b = 2;
        input.c = 3;
        String json = MAPPER_WITH_VIEWS.writerWithView(ViewA.class).writeValueAsString(input);
        assertEquals("[1,null,3]", json);

        // and then that conversely deserializer does something similar
        AsArrayWithView result = MAPPER_WITH_VIEWS.readerFor(AsArrayWithView.class).withView(ViewB.class)
                .readValue("[1,2,3]");
        // should include 'c' (not view-able) and 'b' (include in ViewB) but not 'a'
        assertEquals(3, result.c);
        assertEquals(2, result.b);
        assertEquals(0, result.a);
    }

    @Test
    public void testWithViewAndCreator() throws Exception
    {
        AsArrayWithViewAndCreator result = MAPPER_WITH_VIEWS.readerFor(AsArrayWithViewAndCreator.class)
                .withView(ViewB.class)
                .without(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
                .readValue("[1,2,3]");
        // should include 'c' (not view-able) and 'b' (include in ViewB) but not 'a'
        assertEquals(3, result.c);
        assertEquals(2, result.b);
        assertEquals(0, result.a);
    }

    @Test
    public void testWithCreatorsOrdered() throws Exception
    {
        CreatorAsArray input = new CreatorAsArray(3, 4);
        input.a = 1;
        input.b = 2;

        // note: Creator properties get sorted ahead of others, hence not [1,2,3,4] but:
        String json = MAPPER_WITH_VIEWS.writeValueAsString(input);
        assertEquals("[3,4,1,2]", json);

        // and should get back in proper order, too
        CreatorAsArray output = MAPPER_WITH_VIEWS.readValue(json, CreatorAsArray.class);
        assertEquals(1, output.a);
        assertEquals(2, output.b);
        assertEquals(3, output.x);
        assertEquals(4, output.y);
    }

    // Same as above, but ordering of properties different...
    @Test
    public void testWithCreatorsShuffled() throws Exception
    {
        CreatorAsArrayShuffled input = new CreatorAsArrayShuffled(3, 4);
        input.a = 1;
        input.b = 2;

        // note: explicit ordering overrides implicit creators-first ordering:
        String json = MAPPER_WITH_VIEWS.writeValueAsString(input);
        assertEquals("[1,2,3,4]", json);

        // and should get back in proper order, too
        CreatorAsArrayShuffled output = MAPPER_WITH_VIEWS.readValue(json, CreatorAsArrayShuffled.class);
        assertEquals(1, output.a);
        assertEquals(2, output.b);
        assertEquals(3, output.x);
        assertEquals(4, output.y);
    }

    /*
    /*****************************************************
    /* Tests with Builder pattern
    /*****************************************************
     */

    @Test
    public void testSimpleBuilder() throws Exception
    {
        ValueClassXY value = MAPPER.readValue("[1,2]", ValueClassXY.class);
        assertEquals(2, value._x);
        assertEquals(3, value._y);
    }

    // Won't work, but verify exception
    @Test
    public void testBuilderWithUpdate() throws Exception
    {
        try {
            /*value =*/ MAPPER.readerFor(ValueClassXY.class)
                    .withValueToUpdate(new ValueClassXY(6, 7))
                    .readValue("[1,2]");
            fail("Should not pass");
        } catch (InvalidDefinitionException e) {
            verifyException(e, "Deserialization of");
            verifyException(e, "by passing existing instance");
            verifyException(e, "ValueClassXY");
        }
    }

    // test to ensure @JsonCreator also works with builder
    @Test
    public void testWithCreator() throws Exception
    {
        ObjectReader r = MAPPER.readerFor(CreatorValue.class)
                .without(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);

        CreatorValue value = r.readValue("[1,2,3]");
        assertEquals(1, value.a);
        assertEquals(2, value.b);
        assertEquals(3, value.c);

        // and should be ok with partial too?
        value = r.readValue("[1,2]");
        assertEquals(1, value.a);
        assertEquals(2, value.b);
        assertEquals(0, value.c);

        value = r.readValue("[1]");
        assertEquals(1, value.a);
        assertEquals(0, value.b);
        assertEquals(0, value.c);

        value = r.readValue("[]");
        assertEquals(0, value.a);
        assertEquals(0, value.b);
        assertEquals(0, value.c);
    }

    @Test
    public void testWithCreatorAndView() throws Exception
    {
        ObjectReader reader = MAPPER_WITH_VIEWS.readerFor(CreatorValue.class);

        CreatorValue value;

        // First including values in view
        value = reader.withView(String.class).readValue("[1,2,3]");
        assertEquals(1, value.a);
        assertEquals(2, value.b);
        assertEquals(3, value.c);

        // then not including view
        value = reader.withView(Character.class).readValue("[1,2,3]");
        assertEquals(1, value.a);
        assertEquals(2, value.b);
        assertEquals(0, value.c);
    }

    /*
    /*****************************************************
    /* Failure tests
    /*****************************************************
     */

    // [databind#2077]
    @Test
    public void testPolymorphicAsArray() throws Exception
    {
        String json = MAPPER.writeValueAsString(new DirectLayout());

        Layout instance = MAPPER.readValue(json, Layout.class);
        assertNotNull(instance);
    }

    // [databind#4961]
    @Test
    public void testSerializeArrayWithAnyGetterWithWrapper() throws Exception {
        WrapperForAnyGetter wrapper = new WrapperForAnyGetter();
        wrapper.value = new BeanWithAnyGetter();

        String json = MAPPER.writeValueAsString(wrapper);

        assertEquals(a2q("{\"value\":[\"first\",\"second\",\"forth\",{\"third_A\":\"third_A\",\"third_B\":\"third_B\"}]}"), json);
    }

    // [databind#4961]
    @Test
    public void testSerializeArrayWithAnyGetterAsRoot() throws Exception {
        BeanWithAnyGetter bean = new BeanWithAnyGetter();

        String json = MAPPER.writeValueAsString(bean);

        assertEquals(a2q("[\"first\",\"second\",\"forth\",{\"third_A\":\"third_A\",\"third_B\":\"third_B\"}]"), json);
    }

    // [databind#646]
    @Test
    public void testWithCustomTypeId() throws Exception {

        List<TheItem646.NestedItem> nestedList = new ArrayList<TheItem646.NestedItem>();
        nestedList.add(new TheItem646.NestedItem("foo1"));
        nestedList.add(new TheItem646.NestedItem("foo2"));
        TheItem646 item = new TheItem646("first", false, nestedList);
        Outer646 outer = new Outer646();
        outer.getAttributes().put("entry1", item);

        String json = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(outer);

        Outer646 result = MAPPER.readValue(json, Outer646.class);
        assertNotNull(result);
        assertNotNull(result.attributes);
        assertEquals(1, result.attributes.size());
    }

    @Test
    public void testUnknownExtraProp() throws Exception
    {
        String json = "{\"value\":[true,\"Foobar\",42,13, false]}";
        try {
            MAPPER.readerFor(PojoAsArrayWrapper.class)
                    .with(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                    .readValue(json);
            fail("should not pass with extra element");
        } catch (MismatchedInputException e) {
            verifyException(e, "Unexpected JSON values");
        }

        // but actually fine if skip-unknown set
        PojoAsArrayWrapper v = MAPPER.readerFor(PojoAsArrayWrapper.class)
                .without(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                .readValue(json);
        assertNotNull(v);
        assertEquals(42, v.value.x);
        assertEquals(13, v.value.y);
        assertTrue(v.value.complete);
        assertEquals("Foobar", v.value.name);
    }

    @Test
    public void testBuilderUnknownExtraProp() throws Exception
    {
        String json = "[1, 2, 3, 4]";
        try {
            MAPPER.readerFor(ValueClassXY.class)
                    .with(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                    .readValue(json);
            fail("should not pass with extra element");
        } catch (MismatchedInputException e) {
            // Looks like we get either "Unexpected JSON values" or "Unexpected JSON value(s)"
            verifyException(e, "Unexpected JSON value");
        }

        // but actually fine if skip-unknown set
        ValueClassXY v = MAPPER.readerFor(ValueClassXY.class)
                .without(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                .readValue(json);
        assertNotNull(v);
        // note: +1 for both so
        assertEquals(v._x, 2);
        assertEquals(v._y, 3);
    }
}