diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/api/ValueReader.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/api/ValueReader.java index a50081cb..deee759d 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/api/ValueReader.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/api/ValueReader.java @@ -81,6 +81,16 @@ public Class valueType() { /********************************************************************** */ + /** + * Helper method for getting description of type of values this reader + * produces from input: used for example in exception messages. + * + * @since 2.20 + */ + protected String _valueTypeDesc() { + return _valueType.getCanonicalName(); + } + /** * Helper method for getting description of the token parser currently points to, * for use in descriptions and exception messages. diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/JSONWriter.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/JSONWriter.java index a0cfc2f6..492ee022 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/JSONWriter.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/JSONWriter.java @@ -191,6 +191,15 @@ public void writeField(String fieldName, Object value, int type) throws IOExcept case SER_BOOLEAN_ARRAY: writeBooleanArrayField(fieldName, (boolean[]) value); return; + case SER_SHORT_ARRAY: + writeShortArrayField(fieldName, (short[]) value); + return; + case SER_FLOAT_ARRAY: + writeFloatArrayField(fieldName, (float[]) value); + return; + case SER_DOUBLE_ARRAY: + writeDoubleArrayField(fieldName, (double[]) value); + return; case SER_TREE_NODE: writeTreeNodeField(fieldName, (TreeNode) value); return; @@ -319,6 +328,15 @@ protected void _writeValue(Object value, int type) throws IOException case SER_BOOLEAN_ARRAY: writeBooleanArrayValue((boolean[]) value); return; + case SER_SHORT_ARRAY: + writeShortArrayValue((short[]) value); + return; + case SER_FLOAT_ARRAY: + writeFloatArrayValue((float[]) value); + return; + case SER_DOUBLE_ARRAY: + writeDoubleArrayValue((double[]) value); + return; case SER_TREE_NODE: writeTreeNodeValue((TreeNode) value); return; @@ -427,7 +445,11 @@ protected void _writeValue(Object value, int type) throws IOException protected void writeCollectionValue(Collection v) throws IOException { - _generator.writeStartArray(); + if (v instanceof RandomAccess) { + _generator.writeStartArray(v, v.size()); + } else { + _generator.writeStartArray(v); + } for (Object ob : v) { writeValue(ob); } @@ -442,7 +464,7 @@ protected void writeCollectionField(String fieldName, Collection v) throws IO protected void writeIterableValue(Iterable v) throws IOException { - _generator.writeStartArray(); + _generator.writeStartArray(v); for (Object ob : v) { writeValue(ob); } @@ -457,7 +479,11 @@ protected void writeIterableField(String fieldName, Iterable v) throws IOExce protected void writeListValue(List list) throws IOException { - _generator.writeStartArray(); + if (list instanceof RandomAccess) { + _generator.writeStartArray(list, list.size()); + } else { + _generator.writeStartArray(list); + } for (int i = 0, len = list.size(); i < len; ++i) { Object value = list.get(i); if (value == null) { @@ -477,7 +503,7 @@ protected void writeListField(String fieldName, List v) throws IOException protected void writeMapValue(Map v) throws IOException { - _generator.writeStartObject(); + _generator.writeStartObject(v, v.size()); if (!v.isEmpty()) { for (Map.Entry entry : v.entrySet()) { String key = keyToString(entry.getKey()); @@ -504,8 +530,9 @@ protected void writeMapField(String fieldName, Map v) throws IOException } protected void writeObjectArrayValue(Object[] v) throws IOException { - _generator.writeStartArray(); - for (int i = 0, len = v.length; i < len; ++i) { + final int len = v.length; + _generator.writeStartArray(v, len); + for (int i = 0; i < len; ++i) { writeValue(v[i]); } _generator.writeEndArray(); @@ -517,11 +544,7 @@ protected void writeObjectArrayField(String fieldName, Object[] v) throws IOExce } protected void writeIntArrayValue(int[] v) throws IOException { - _generator.writeStartArray(); - for (int i = 0, len = v.length; i < len; ++i) { - _generator.writeNumber(v[i]); - } - _generator.writeEndArray(); + _generator.writeArray(v, 0, v.length); } protected void writeIntArrayField(String fieldName, int[] v) throws IOException { @@ -530,11 +553,7 @@ protected void writeIntArrayField(String fieldName, int[] v) throws IOException } protected void writeLongArrayValue(long[] v) throws IOException { - _generator.writeStartArray(); - for (int i = 0, len = v.length; i < len; ++i) { - _generator.writeNumber(v[i]); - } - _generator.writeEndArray(); + _generator.writeArray(v, 0, v.length); } protected void writeLongArrayField(String fieldName, long[] v) throws IOException { @@ -543,7 +562,7 @@ protected void writeLongArrayField(String fieldName, long[] v) throws IOExceptio } protected void writeBooleanArrayValue(boolean[] v) throws IOException { - _generator.writeStartArray(); + _generator.writeStartArray(v, v.length); for (int i = 0, len = v.length; i < len; ++i) { _generator.writeBoolean(v[i]); } @@ -555,6 +574,41 @@ protected void writeBooleanArrayField(String fieldName, boolean[] v) throws IOEx writeBooleanArrayValue(v); } + protected void writeShortArrayValue(short[] v) throws IOException { + _generator.writeStartArray(v, v.length); + for (int i = 0, len = v.length; i < len; ++i) { + _generator.writeNumber(v[i]); + } + _generator.writeEndArray(); + } + + protected void writeShortArrayField(String fieldName, short[] v) throws IOException { + _generator.writeFieldName(fieldName); + writeShortArrayValue(v); + } + + protected void writeFloatArrayValue(float[] v) throws IOException { + _generator.writeStartArray(v, v.length); + for (int i = 0, len = v.length; i < len; ++i) { + _generator.writeNumber(v[i]); + } + _generator.writeEndArray(); + } + + protected void writeFloatArrayField(String fieldName, float[] v) throws IOException { + _generator.writeFieldName(fieldName); + writeFloatArrayValue(v); + } + + protected void writeDoubleArrayValue(double[] v) throws IOException { + _generator.writeArray(v, 0, v.length); + } + + protected void writeDoubleArrayField(String fieldName, double[] v) throws IOException { + _generator.writeFieldName(fieldName); + writeDoubleArrayValue(v); + } + protected void writeTreeNodeValue(TreeNode v) throws IOException { if (_treeCodec == null) { throw new JSONObjectException("No `TreeCodec` configured: can not serialize `TreeNode` values"); diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/SimpleValueReader.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/SimpleValueReader.java index 138ed249..00460236 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/SimpleValueReader.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/SimpleValueReader.java @@ -114,9 +114,14 @@ public Object read(JSONReader reader, JsonParser p) throws IOException return _readIntArray(p); case SER_LONG_ARRAY: return _readLongArray(p); - //case SER_BOOLEAN_ARRAY: - // TODO: - + + // Not yet supported: + case SER_BOOLEAN_ARRAY: + case SER_SHORT_ARRAY: + case SER_FLOAT_ARRAY: + case SER_DOUBLE_ARRAY: + throw JSONObjectException.from(p, + "Deserialization of `"+_valueTypeDesc()+"` not yet supported"); case SER_TREE_NODE: return reader.readTree(); @@ -271,7 +276,7 @@ public Object read(JSONReader reader, JsonParser p) throws IOException } throw JSONObjectException.from(p, - "Can not create a `"+_valueType.getName()+"` instance out of "+_tokenDesc(p)); + "Can not create a `"+_valueTypeDesc()+"` instance out of "+_tokenDesc(p)); } /* @@ -374,6 +379,6 @@ protected long _fetchLong(JsonParser p) throws IOException return p.getLongValue(); } throw JSONObjectException.from(p, "Can not get long numeric value from JSON (to construct " - +_valueType.getName()+") from "+_tokenDesc(p, t)); + +_valueTypeDesc()+") from "+_tokenDesc(p, t)); } } diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueLocatorBase.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueLocatorBase.java index a2e18bc3..e9a28d28 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueLocatorBase.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueLocatorBase.java @@ -58,61 +58,64 @@ public abstract class ValueLocatorBase */ public final static int SER_OBJECT_ARRAY = 4; - public final static int SER_INT_ARRAY = 5; - public final static int SER_LONG_ARRAY = 6; - public final static int SER_BOOLEAN_ARRAY = 7; + public final static int SER_SHORT_ARRAY = 5; // since 2.20 + public final static int SER_INT_ARRAY = 6; + public final static int SER_LONG_ARRAY = 7; + public final static int SER_FLOAT_ARRAY = 8; // since 2.20 + public final static int SER_DOUBLE_ARRAY = 9; // since 2.20 + public final static int SER_BOOLEAN_ARRAY = 10; /** * An implementation of {@link com.fasterxml.jackson.core.TreeNode} */ - public final static int SER_TREE_NODE = 8; + public final static int SER_TREE_NODE = 11; // // // String(-like) types - public final static int SER_STRING = 9; - public final static int SER_CHARACTER_SEQUENCE = 10; - public final static int SER_CHAR_ARRAY = 11; - public final static int SER_BYTE_ARRAY = 12; + public final static int SER_STRING = 12; + public final static int SER_CHARACTER_SEQUENCE = 13; + public final static int SER_CHAR_ARRAY = 14; + public final static int SER_BYTE_ARRAY = 15; // // // Numbers - public final static int SER_NUMBER_BYTE = 13; + public final static int SER_NUMBER_BYTE = 16; - public final static int SER_NUMBER_SHORT = 14; + public final static int SER_NUMBER_SHORT = 17; - public final static int SER_NUMBER_INTEGER = 15; - public final static int SER_NUMBER_INTEGER_WRAPPER = 16; + public final static int SER_NUMBER_INTEGER = 18; + public final static int SER_NUMBER_INTEGER_WRAPPER = 19; - public final static int SER_NUMBER_LONG = 17; - public final static int SER_NUMBER_LONG_WRAPPER = 18; + public final static int SER_NUMBER_LONG = 20; + public final static int SER_NUMBER_LONG_WRAPPER = 21; - public final static int SER_NUMBER_FLOAT = 19; - public final static int SER_NUMBER_FLOAT_WRAPPER = 20; + public final static int SER_NUMBER_FLOAT = 22; + public final static int SER_NUMBER_FLOAT_WRAPPER = 23; - public final static int SER_NUMBER_DOUBLE = 21; - public final static int SER_NUMBER_DOUBLE_WRAPPER = 22; + public final static int SER_NUMBER_DOUBLE = 24; + public final static int SER_NUMBER_DOUBLE_WRAPPER = 25; - public final static int SER_NUMBER_BIG_INTEGER = 23; + public final static int SER_NUMBER_BIG_INTEGER = 26; - public final static int SER_NUMBER_BIG_DECIMAL = 24; + public final static int SER_NUMBER_BIG_DECIMAL = 27; // // // Other specific scalar types - public final static int SER_BOOLEAN = 25; - public final static int SER_BOOLEAN_WRAPPER = 26; - public final static int SER_CHAR = 27; + public final static int SER_BOOLEAN = 28; + public final static int SER_BOOLEAN_WRAPPER = 29; + public final static int SER_CHAR = 30; - public final static int SER_ENUM = 28; + public final static int SER_ENUM = 31; - public final static int SER_DATE = 29; - public final static int SER_CALENDAR = 30; + public final static int SER_DATE = 32; + public final static int SER_CALENDAR = 33; - public final static int SER_CLASS = 31; - public final static int SER_FILE = 32; - public final static int SER_UUID = 33; - public final static int SER_URL = 34; - public final static int SER_URI = 35; - public final static int SER_PATH = 36; // since 2.17 + public final static int SER_CLASS = 34; + public final static int SER_FILE = 35; + public final static int SER_UUID = 36; + public final static int SER_URL = 37; + public final static int SER_URI = 38; + public final static int SER_PATH = 39; // since 2.17 // // // Iterate-able types @@ -120,7 +123,7 @@ public abstract class ValueLocatorBase * Anything that implements {@link java.lang.Iterable}, but not * {@link java.util.Collection}. */ - public final static int SER_ITERABLE = 37; + public final static int SER_ITERABLE = 40; /* /********************************************************************** @@ -159,6 +162,15 @@ protected int _findSimpleType(Class raw, boolean forSer) if (raw == boolean[].class) { return SER_BOOLEAN_ARRAY; } + if (raw == short[].class) { + return SER_SHORT_ARRAY; + } + if (raw == float[].class) { + return SER_FLOAT_ARRAY; + } + if (raw == double[].class) { + return SER_DOUBLE_ARRAY; + } // Hmmh. Could support all types; add as/when needed return SER_UNKNOWN; } diff --git a/jr-objects/src/test/java/com/fasterxml/jackson/jr/ob/PrimitiveArrayTest.java b/jr-objects/src/test/java/com/fasterxml/jackson/jr/ob/PrimitiveArrayTest.java new file mode 100644 index 00000000..9e697f9f --- /dev/null +++ b/jr-objects/src/test/java/com/fasterxml/jackson/jr/ob/PrimitiveArrayTest.java @@ -0,0 +1,196 @@ +package com.fasterxml.jackson.jr.ob; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.jr.testutil.failure.JacksonTestFailureExpected; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class PrimitiveArrayTest extends TestBase +{ + // Test arrays as object fields + static class AllArraysBean { + public boolean[] booleans; + public byte[] bytes; + public char[] chars; + public short[] shorts; + public int[] ints; + public long[] longs; + public float[] floats; + public double[] doubles; + } + + // Test all 7 primitive array types: boolean[], byte[], short[], int[], long[], float[], double[] + // Also test char[] which is handled specially (as String) + + private final static String BOOLEAN_ARRAY_JSON = "[true,false,true,false,true]"; + private final static boolean[] BOOLEAN_ARRAY = new boolean[] { true, false, true, false, true }; + + // Not yet implemented in Jackson-jr + @JacksonTestFailureExpected + @Test + public void testBooleanArrayRead() throws Exception { + assertArrayEquals(BOOLEAN_ARRAY, JSON.std.beanFrom(boolean[].class, BOOLEAN_ARRAY_JSON)); + } + + @Test + public void testBooleanArrayWrite() throws Exception { + assertEquals(BOOLEAN_ARRAY_JSON, JSON.std.asString(BOOLEAN_ARRAY)); + } + + // Special: uses Base64 encoding for byte arrays + @Test + public void testByteArrayReadWrite() throws Exception { + final byte[] input = new byte[]{1, 2, 3, 127, -128}; + String json = JSON.std.asString(input); + byte[] result = JSON.std.beanFrom(byte[].class, json); + assertArrayEquals(input, result); + } + + // Special: char[] is serialized as a String + @Test + public void testCharArray() throws Exception { + final char[] input = new char[]{'a', 'b', 'c', 'X', 'Y', 'Z'}; + String json = JSON.std.asString(input); + char[] result = JSON.std.beanFrom(char[].class, json); + assertArrayEquals(input, result); + } + + private final static String SHORT_ARRAY_JSON = "[1,2,3,32767,-32768]"; + private final static short[] SHORT_ARRAY = new short[] { 1, 2, 3, 32767, -32768 }; + + // Not yet implemented in Jackson-jr + @Test + public void testShortArrayRead() throws Exception { +// assertArrayEquals(SHORT_ARRAY, JSON.std.beanFrom(short[].class, SHORT_ARRAY_JSON)); + try { + JSON.std.beanFrom(short[].class, SHORT_ARRAY_JSON); + fail("Should not pass"); + } catch (JSONObjectException e) { + verifyException(e, "Deserialization of `short[]` not yet supported"); + } + } + + @Test + public void testShortArrayWrite() throws Exception { + assertEquals(SHORT_ARRAY_JSON, JSON.std.asString(SHORT_ARRAY)); + } + + private final static String INT_ARRAY_JSON = "[1,2,-2000,1000000,-999999999]"; + private final static int[] INT_ARRAY = new int[] {1,2,-2000,1000000,-999999999}; + + @Test + public void testIntArrayRead() throws Exception { + assertArrayEquals(INT_ARRAY, JSON.std.beanFrom(int[].class, INT_ARRAY_JSON)); + } + + @Test + public void testIntArrayWrite() throws Exception { + assertEquals(INT_ARRAY_JSON, JSON.std.asString(INT_ARRAY)); + } + + private final static String LONG_ARRAY_JSON = "[1,-2,3,999999999999,-999999999999]"; + private final static long[] LONG_ARRAY = new long[] {1L,-2L,3L,999999999999L,-999999999999L}; + + @Test + public void testLongArrayRead() throws Exception { + assertArrayEquals(LONG_ARRAY, JSON.std.beanFrom(long[].class, LONG_ARRAY_JSON)); + } + + @Test + public void testLongArrayWrite() throws Exception { + assertEquals(LONG_ARRAY_JSON, JSON.std.asString(LONG_ARRAY)); + } + + private final static String FLOAT_ARRAY_JSON = "[1.0,2.5,3.125,-5.5,0.0]"; + private final static float[] FLOAT_ARRAY = new float[] {1.0f, 2.5f, 3.125f, -5.5f, 0.0f}; + + // Not yet implemented in Jackson-jr + @Test + public void testFloatArrayRead() throws Exception { + //assertArrayEquals(FLOAT_ARRAY, JSON.std.beanFrom(float[].class, FLOAT_ARRAY_JSON), + // 0.00001f); + + try { + JSON.std.beanFrom(float[].class, FLOAT_ARRAY_JSON); + fail("Should not pass"); + } catch (JSONObjectException e) { + verifyException(e, "Deserialization of `float[]` not yet supported"); + } + } + + @Test + public void testFloatArrayWrite() throws Exception { + assertEquals(FLOAT_ARRAY_JSON, JSON.std.asString(FLOAT_ARRAY)); + } + + private final static String DOUBLE_ARRAY_JSON = "[0.5,-2.25,3.14159,-5.5,0.0]"; + private final static double[] DOUBLE_ARRAY = new double[] {0.5, -2.25, 3.14159, -5.5f, 0.0}; + + @Test + public void testDoubleArrayRead() throws Exception { + //assertArrayEquals(DOUBLE_ARRAY, JSON.std.beanFrom(double[].class, DOUBLE_ARRAY_JSON), + // 0.00001); + + try { + JSON.std.beanFrom(double[].class, DOUBLE_ARRAY_JSON); + fail("Should not pass"); + } catch (JSONObjectException e) { + verifyException(e, "Deserialization of `double[]` not yet supported"); + } + } + + @Test + public void testDoubleArrayWrite() throws Exception { + assertEquals(DOUBLE_ARRAY_JSON, JSON.std.asString(DOUBLE_ARRAY)); + } + + // Test empty arrays + // Not yet implemented in Jackson-jr + @Test + public void testEmptyArrays() throws Exception { + assertArrayEquals(new char[0], JSON.std.beanFrom(char[].class, "\"\"")); + assertArrayEquals(new int[0], JSON.std.beanFrom(int[].class, "[]")); + assertArrayEquals(new long[0], JSON.std.beanFrom(long[].class, "[]")); + } + + // Not yet implemented in Jackson-jr + @JacksonTestFailureExpected + @Test + public void testEmptyArraysFailing() throws Exception { + assertArrayEquals(new boolean[0], JSON.std.beanFrom(boolean[].class, "[]")); + assertArrayEquals(new byte[0], JSON.std.beanFrom(byte[].class, "[]")); + assertArrayEquals(new short[0], JSON.std.beanFrom(short[].class, "[]")); + assertArrayEquals(new float[0], JSON.std.beanFrom(float[].class, "[]"), 0.0f); + assertArrayEquals(new double[0], JSON.std.beanFrom(double[].class, "[]"), 0.0); + } + + // Not yet fully implemented in Jackson-jr + @JacksonTestFailureExpected + @Test + public void testArraysAsObjectFields() throws Exception { + AllArraysBean input = new AllArraysBean(); + input.booleans = new boolean[]{true, false}; + input.bytes = new byte[]{1, 2, 3}; + input.chars = new char[]{'a', 'b'}; + input.shorts = new short[]{10, 20}; + input.ints = new int[]{100, 200}; + input.longs = new long[]{1000L, 2000L}; + input.floats = new float[]{1.5f, 2.5f}; + input.doubles = new double[]{10.5, 20.5}; + + String json = JSON.std.asString(input); + AllArraysBean result = JSON.std.beanFrom(AllArraysBean.class, json); + + assertArrayEquals(input.booleans, result.booleans); + assertArrayEquals(input.bytes, result.bytes); + assertArrayEquals(input.chars, result.chars); + assertArrayEquals(input.shorts, result.shorts); + assertArrayEquals(input.ints, result.ints); + assertArrayEquals(input.longs, result.longs); + assertArrayEquals(input.floats, result.floats, 0.0001f); + assertArrayEquals(input.doubles, result.doubles, 0.0000001); + } +} diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index bce4274e..97e59c76 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -75,3 +75,8 @@ Giovanni van der Schelde (@Giovds) * Reported, suggested a fix for #167: Deserialization of record fails on constructor parameter ordering (2.18.1) + +Luke Hutchison (@lukehutch) + +* Reported #196: `float[]` and `double[]` are serialized to JSON as `{ }` + (2.20.0) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index a7a7ce7b..29b367a5 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -11,6 +11,9 @@ Modules: === Releases === ------------------------------------------------------------------------ +#196: `float[]` and `double[]` are serialized to JSON as `{ }` + (contributed by Luke H) + 2.20.0-rc1 (04-Aug-2025) No changes since 2.19