package com.fasterxml.jackson.databind.deser; import java.io.*; import java.util.*; import static org.junit.Assert.*; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; import com.fasterxml.jackson.databind.module.SimpleModule; /** * This unit test suite tries to verify that the "Native" java type * mapper can properly re-construct Java array objects from Json arrays. */ public class TestArrayDeserialization extends BaseMapTest { public final static class Bean1 { int _x, _y; List<Bean2> _beans; // Just for deserialization: @SuppressWarnings("unused") private Bean1() { } public Bean1(int x, int y, List<Bean2> beans) { _x = x; _y = y; _beans = beans; } public int getX() { return _x; } public int getY() { return _y; } public List<Bean2> getBeans() { return _beans; } public void setX(int x) { _x = x; } public void setY(int y) { _y = y; } public void setBeans(List<Bean2> b) { _beans = b; } @Override public boolean equals(Object o) { if (!(o instanceof Bean1)) return false; Bean1 other = (Bean1) o; return (_x == other._x) && (_y == other._y) && _beans.equals(other._beans) ; } } /** * Simple bean that just gets serialized as a String value. * Deserialization from String value will be done via single-arg * constructor. */ public final static class Bean2 implements JsonSerializable // so we can output as simple String { final String _desc; public Bean2(String d) { _desc = d; } @Override public void serialize(JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException { jgen.writeString(_desc); } @Override public String toString() { return _desc; } @Override public boolean equals(Object o) { if (!(o instanceof Bean2)) return false; Bean2 other = (Bean2) o; return _desc.equals(other._desc); } @Override public void serializeWithType(JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) throws IOException, JsonProcessingException { } } static class ObjectWrapper { public Object wrapped; } static class ObjectArrayWrapper { public Object[] wrapped; } static class CustomNonDeserArrayDeserializer extends JsonDeserializer<NonDeserializable[]> { @Override public NonDeserializable[] deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { List<NonDeserializable> list = new ArrayList<NonDeserializable>(); while (jp.nextToken() != JsonToken.END_ARRAY) { list.add(new NonDeserializable(jp.getText(), false)); } return list.toArray(new NonDeserializable[list.size()]); } } static class NonDeserializable { protected String value; public NonDeserializable(String v, boolean bogus) { value = v; } } static class Product { public String name; public List<Things> thelist; } static class Things { public String height; public String width; } static class HiddenBinaryBean890 { @JsonDeserialize(as=byte[].class) public Object someBytes; } /* /********************************************************** /* Tests for "untyped" arrays, Object[] /********************************************************** */ private final ObjectMapper MAPPER = new ObjectMapper(); public void testUntypedArray() throws Exception { // to get "untyped" default map-to-map, pass Object[].class String JSON = "[ 1, null, \"x\", true, 2.0 ]"; Object[] result = MAPPER.readValue(JSON, Object[].class); assertNotNull(result); assertEquals(5, result.length); assertEquals(Integer.valueOf(1), result[0]); assertNull(result[1]); assertEquals("x", result[2]); assertEquals(Boolean.TRUE, result[3]); assertEquals(Double.valueOf(2.0), result[4]); } public void testIntegerArray() throws Exception { final int LEN = 90000; // Let's construct array to get it big enough StringBuilder sb = new StringBuilder(); sb.append('['); for (int i = 0; i < LEN; ++i) { if (i > 0) { sb.append(','); } sb.append(i); } sb.append(']'); Integer[] result = MAPPER.readValue(sb.toString(), Integer[].class); assertNotNull(result); assertEquals(LEN, result.length); for (int i = 0; i < LEN; ++i) { assertEquals(i, result[i].intValue()); } } // [JACKSON-620]: allow "" to mean 'null' for Arrays, List and Maps public void testFromEmptyString() throws Exception { ObjectMapper m = new ObjectMapper(); m.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true); assertNull(m.readValue(quote(""), Object[].class)); assertNull( m.readValue(quote(""), String[].class)); assertNull( m.readValue(quote(""), int[].class)); } // [JACKSON-620]: allow "" to mean 'null' for Arrays, List and Maps public void testFromEmptyString2() throws Exception { ObjectMapper m = new ObjectMapper(); m.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true); m.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); Product p = m.readValue("{\"thelist\":\"\"}", Product.class); assertNotNull(p); assertNull(p.thelist); } /* /********************************************************** /* Arrays of arrays... /********************************************************** */ public void testUntypedArrayOfArrays() throws Exception { // to get "untyped" default map-to-map, pass Object[].class final String JSON = "[[[-0.027512,51.503221],[-0.008497,51.503221],[-0.008497,51.509744],[-0.027512,51.509744]]]"; Object result = MAPPER.readValue(JSON, Object.class); assertEquals(ArrayList.class, result.getClass()); assertNotNull(result); // Should be able to get it as an Object array as well Object[] array = MAPPER.readValue(JSON, Object[].class); assertNotNull(array); assertEquals(Object[].class, array.getClass()); // and as wrapped variants too ObjectWrapper w = MAPPER.readValue("{\"wrapped\":"+JSON+"}", ObjectWrapper.class); assertNotNull(w); assertNotNull(w.wrapped); assertEquals(ArrayList.class, w.wrapped.getClass()); ObjectArrayWrapper aw = MAPPER.readValue("{\"wrapped\":"+JSON+"}", ObjectArrayWrapper.class); assertNotNull(aw); assertNotNull(aw.wrapped); } /* /********************************************************** /* Tests for String arrays, char[] /********************************************************** */ public void testStringArray() throws Exception { final String[] STRS = new String[] { "a", "b", "abcd", "", "???", "\"quoted\"", "lf: \n", }; StringWriter sw = new StringWriter(); JsonGenerator jg = MAPPER.getFactory().createGenerator(sw); jg.writeStartArray(); for (String str : STRS) { jg.writeString(str); } jg.writeEndArray(); jg.close(); String[] result = MAPPER.readValue(sw.toString(), String[].class); assertNotNull(result); assertEquals(STRS.length, result.length); for (int i = 0; i < STRS.length; ++i) { assertEquals(STRS[i], result[i]); } // [#479]: null handling was busted in 2.4.0 result = MAPPER.readValue(" [ null ]", String[].class); assertNotNull(result); assertEquals(1, result.length); assertNull(result[0]); } public void testCharArray() throws Exception { final String TEST_STR = "Let's just test it? Ok!"; char[] result = MAPPER.readValue("\""+TEST_STR+"\"", char[].class); assertEquals(TEST_STR, new String(result)); // And just for [JACKSON-289], let's verify that fluffy arrays work too result = MAPPER.readValue("[\"a\",\"b\",\"c\"]", char[].class); assertEquals("abc", new String(result)); } /* /********************************************************** /* Tests for primitive arrays /********************************************************** */ public void testBooleanArray() throws Exception { boolean[] result = MAPPER.readValue("[ true, false, false ]", boolean[].class); assertNotNull(result); assertEquals(3, result.length); assertTrue(result[0]); assertFalse(result[1]); assertFalse(result[2]); } public void testByteArrayAsNumbers() throws Exception { final int LEN = 37000; StringBuilder sb = new StringBuilder(); sb.append('['); for (int i = 0; i < LEN; ++i) { int value = i - 128; sb.append((value < 256) ? value : (value & 0x7F)); sb.append(','); } sb.append("0]"); byte[] result = MAPPER.readValue(sb.toString(), byte[].class); assertNotNull(result); assertEquals(LEN+1, result.length); for (int i = 0; i < LEN; ++i) { int value = i - 128; byte exp = (byte) ((value < 256) ? value : (value & 0x7F)); if (exp != result[i]) { fail("At offset #"+i+" ("+result.length+"), expected "+exp+", got "+result[i]); } assertEquals(exp, result[i]); } assertEquals(0, result[LEN]); } public void testByteArrayAsBase64() throws Exception { /* Hmmh... let's use JsonGenerator here, to hopefully ensure we * get proper base64 encoding. Plus, not always using that * silly sample from Wikipedia. */ JsonFactory jf = new JsonFactory(); StringWriter sw = new StringWriter(); int LEN = 9000; byte[] TEST = new byte[LEN]; for (int i = 0; i < LEN; ++i) { TEST[i] = (byte) i; } JsonGenerator jg = jf.createGenerator(sw); jg.writeBinary(TEST); jg.close(); String inputData = sw.toString(); byte[] result = MAPPER.readValue(inputData, byte[].class); assertNotNull(result); assertArrayEquals(TEST, result); } /** * And then bit more challenging case; let's try decoding * multiple byte arrays from an array... */ public void testByteArraysAsBase64() throws Exception { JsonFactory jf = new JsonFactory(); StringWriter sw = new StringWriter(1000); final int entryCount = 15; JsonGenerator jg = jf.createGenerator(sw); jg.writeStartArray(); byte[][] entries = new byte[entryCount][]; for (int i = 0; i < entryCount; ++i) { byte[] b = new byte[1000 - i * 20]; for (int x = 0; x < b.length; ++x) { b[x] = (byte) (i + x); } entries[i] = b; jg.writeBinary(b); } jg.writeEndArray(); jg.close(); String inputData = sw.toString(); byte[][] result = MAPPER.readValue(inputData, byte[][].class); assertNotNull(result); assertEquals(entryCount, result.length); for (int i = 0; i < entryCount; ++i) { byte[] b = result[i]; assertArrayEquals("Comparing entry #"+i+"/"+entryCount,entries[i], b); } } // [JACKSON-763] public void testByteArraysWith763() throws Exception { String[] input = new String[] { "YQ==", "Yg==", "Yw==" }; byte[][] data = MAPPER.convertValue(input, byte[][].class); assertEquals("a", new String(data[0], "US-ASCII")); assertEquals("b", new String(data[1], "US-ASCII")); assertEquals("c", new String(data[2], "US-ASCII")); } public void testShortArray() throws Exception { final int LEN = 31001; // fits in signed 16-bit StringBuilder sb = new StringBuilder(); sb.append('['); for (int i = 0; i < LEN; ++i) { if (i > 0) { sb.append(','); } sb.append(i); } sb.append(']'); short[] result = MAPPER.readValue(sb.toString(), short[].class); assertNotNull(result); assertEquals(LEN, result.length); for (int i = 0; i < LEN; ++i) { short exp = (short) i; assertEquals(exp, result[i]); } } public void testIntArray() throws Exception { final int LEN = 70000; // Let's construct array to get it big enough StringBuilder sb = new StringBuilder(); sb.append('['); for (int i = 0; i < LEN; ++i) { if (i > 0) { sb.append(','); } sb.append(-i); } sb.append(']'); int[] result = MAPPER.readValue(sb.toString(), int[].class); assertNotNull(result); assertEquals(LEN, result.length); for (int i = 0; i < LEN; ++i) { assertEquals(-i, result[i]); } } public void testLongArray() throws Exception { final int LEN = 12300; StringBuilder sb = new StringBuilder(); sb.append('['); for (int i = 0; i < LEN; ++i) { if (i > 0) { sb.append(','); } sb.append(i); } sb.append(']'); long[] result = MAPPER.readValue(sb.toString(), long[].class); assertNotNull(result); assertEquals(LEN, result.length); for (int i = 0; i < LEN; ++i) { long exp = (long) i; assertEquals(exp, result[i]); } } public void testDoubleArray() throws Exception { final int LEN = 7000; StringBuilder sb = new StringBuilder(); sb.append('['); for (int i = 0; i < LEN; ++i) { // not ideal, but has to do... if (i > 0) { sb.append(','); } sb.append(i).append('.').append(i % 10); } sb.append(']'); double[] result = MAPPER.readValue(sb.toString(), double[].class); assertNotNull(result); assertEquals(LEN, result.length); for (int i = 0; i < LEN; ++i) { String expStr = String.valueOf(i) + "." + String.valueOf(i % 10); String actStr = String.valueOf(result[i]); if (!expStr.equals(actStr)) { fail("Entry #"+i+"/"+LEN+"; exp '"+expStr+"', got '"+actStr+"'"); } } } public void testFloatArray() throws Exception { final int LEN = 7000; StringBuilder sb = new StringBuilder(); sb.append('['); for (int i = 0; i < LEN; ++i) { if (i > 0) { sb.append(','); } // not ideal, but has to do... sb.append(i).append('.').append(i % 10); } sb.append(']'); float[] result = MAPPER.readValue(sb.toString(), float[].class); assertNotNull(result); assertEquals(LEN, result.length); for (int i = 0; i < LEN; ++i) { String expStr = String.valueOf(i) + "." + String.valueOf(i % 10); assertEquals(expStr, String.valueOf(result[i])); } } /* /********************************************************** /* Tests for Bean arrays /********************************************************** */ public void testBeanArray() throws Exception { List<Bean1> src = new ArrayList<Bean1>(); List<Bean2> b2 = new ArrayList<Bean2>(); b2.add(new Bean2("a")); b2.add(new Bean2("foobar")); src.add(new Bean1(1, 2, b2)); b2 = new ArrayList<Bean2>(); b2.add(null); src.add(new Bean1(4, 5, b2)); // Ok: let's assume bean serializer works ok.... StringWriter sw = new StringWriter(); MAPPER.writeValue(sw, src); // And then test de-serializer List<Bean1> result = MAPPER.readValue(sw.toString(), new TypeReference<List<Bean1>>() { }); assertNotNull(result); assertEquals(src, result); } /* /********************************************************** /* And special cases for byte array (base64 encoded) /********************************************************** */ // for [databind#890] public void testByteArrayTypeOverride890() throws Exception { HiddenBinaryBean890 result = MAPPER.readValue( aposToQuotes("{'someBytes':'AQIDBA=='}"), HiddenBinaryBean890.class); assertNotNull(result); assertNotNull(result.someBytes); assertEquals(byte[].class, result.someBytes.getClass()); } /* /********************************************************** /* And custom deserializers too /********************************************************** */ public void testCustomDeserializers() throws Exception { ObjectMapper mapper = new ObjectMapper(); SimpleModule testModule = new SimpleModule("test", Version.unknownVersion()); testModule.addDeserializer(NonDeserializable[].class, new CustomNonDeserArrayDeserializer()); mapper.registerModule(testModule); NonDeserializable[] result = mapper.readValue("[\"a\"]", NonDeserializable[].class); assertNotNull(result); assertEquals(1, result.length); assertEquals("a", result[0].value); } }