diff --git a/driver/src/main/java/org/neo4j/driver/Record.java b/driver/src/main/java/org/neo4j/driver/Record.java index cd662ecf4e..a76e5be281 100644 --- a/driver/src/main/java/org/neo4j/driver/Record.java +++ b/driver/src/main/java/org/neo4j/driver/Record.java @@ -129,6 +129,8 @@ public interface Record extends MapAccessorWithDefaultValue { * {@code null} value (this includes primitive types), an alternative constructor that excludes it must be * available. *

+ * The mapping only works for types with directly accessible constructors, not interfaces or abstract types. + *

* Types with generic parameters defined at the class level are not supported. However, constructor arguments with * specific types are permitted, see the {@code actors} parameter in the example above. *

diff --git a/driver/src/main/java/org/neo4j/driver/Value.java b/driver/src/main/java/org/neo4j/driver/Value.java index 6a7d872e51..744d41566d 100644 --- a/driver/src/main/java/org/neo4j/driver/Value.java +++ b/driver/src/main/java/org/neo4j/driver/Value.java @@ -545,11 +545,11 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue { * * * {@link TypeSystem#STRING} - * {@link String} + * {@link String}, {@code char}, {@link Character} * * * {@link TypeSystem#INTEGER} - * {@code long}, {@link Long}, {@code int}, {@link Integer}, {@code double}, {@link Double}, {@code float}, {@link Float} + * {@code long}, {@link Long}, {@code int}, {@link Integer}, {@code short}, {@link Short}, {@code double}, {@link Double}, {@code float}, {@link Float} * * * {@link TypeSystem#FLOAT} @@ -594,7 +594,9 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue { * * * {@link TypeSystem#LIST} - * {@link List} + * {@link List}, {@code T[]} as long as list elements may be mapped to the array component type + * (for example, {@code char[]}, {@code boolean[]}, {@code String[]}, {@code long[]}, {@code int[]}, + * {@code short[]}, {@code double[]}, {@code float[]}) * * * {@link TypeSystem#MAP} @@ -663,6 +665,8 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue { * {@code null} value (this includes primitive types), an alternative constructor that excludes it must be * available. *

+ * The mapping only works for types with directly accessible constructors, not interfaces or abstract types. + *

* Example with optional property (using the Neo4j Movies Database): *

      * {@code
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/IntegerValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/IntegerValue.java
index 0211135a09..3588b7ffea 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/IntegerValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/IntegerValue.java
@@ -85,10 +85,21 @@ public  T as(Class targetClass) {
             return (T) Float.valueOf(asFloat());
         } else if (targetClass.equals(Float.class)) {
             return targetClass.cast(asFloat());
+        } else if (targetClass.equals(short.class)) {
+            return (T) Short.valueOf(asShort());
+        } else if (targetClass.equals(Short.class)) {
+            return targetClass.cast(asShort());
         }
         throw new Uncoercible(type().name(), targetClass.getCanonicalName());
     }
 
+    private short asShort() {
+        if (val > Short.MAX_VALUE || val < Short.MIN_VALUE) {
+            throw new LossyCoercion(type().name(), "Java short");
+        }
+        return (short) val;
+    }
+
     @Override
     public String toString() {
         return Long.toString(val);
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/ListValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/ListValue.java
index acabe97626..9809ac71ac 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/ListValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/ListValue.java
@@ -18,6 +18,7 @@
 
 import static org.neo4j.driver.Values.ofObject;
 
+import java.lang.reflect.Array;
 import java.lang.reflect.ParameterizedType;
 import java.util.Arrays;
 import java.util.Iterator;
@@ -26,6 +27,7 @@
 import org.neo4j.driver.Value;
 import org.neo4j.driver.Values;
 import org.neo4j.driver.exceptions.value.Uncoercible;
+import org.neo4j.driver.exceptions.value.ValueException;
 import org.neo4j.driver.internal.types.InternalTypeSystem;
 import org.neo4j.driver.internal.util.Extract;
 import org.neo4j.driver.types.Type;
@@ -64,6 +66,22 @@ public  List asList(Function mapFunction) {
     public  T as(Class targetClass) {
         if (targetClass.isAssignableFrom(List.class)) {
             return targetClass.cast(asList());
+        } else if (targetClass.isArray()) {
+            var componentType = targetClass.componentType();
+            var array = Array.newInstance(componentType, values.length);
+            for (var i = 0; i < values.length; i++) {
+                Object value;
+                try {
+                    value = values[i].as(componentType);
+                } catch (Throwable throwable) {
+                    throw new ValueException(
+                            "Failed to map LIST value to %s - an error occured while mapping the element at index %d"
+                                    .formatted(targetClass.getCanonicalName(), i),
+                            throwable);
+                }
+                Array.set(array, i, value);
+            }
+            return targetClass.cast(array);
         }
         throw new Uncoercible(type().name(), targetClass.getCanonicalName());
     }
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/StringValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/StringValue.java
index 75c92f03f7..49a17810d0 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/StringValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/StringValue.java
@@ -17,6 +17,7 @@
 package org.neo4j.driver.internal.value;
 
 import java.util.Objects;
+import org.neo4j.driver.exceptions.value.LossyCoercion;
 import org.neo4j.driver.exceptions.value.Uncoercible;
 import org.neo4j.driver.internal.types.InternalTypeSystem;
 import org.neo4j.driver.types.Type;
@@ -51,10 +52,17 @@ public String asString() {
         return val;
     }
 
+    @SuppressWarnings("unchecked")
     @Override
     public  T as(Class targetClass) {
         if (targetClass.isAssignableFrom(String.class)) {
             return targetClass.cast(asString());
+        } else if ((targetClass.isAssignableFrom(char.class) || targetClass.isAssignableFrom(Character.class))) {
+            if (val.length() == 1) {
+                return (T) Character.valueOf(val.charAt(0));
+            } else {
+                throw new LossyCoercion(type().name(), targetClass.getCanonicalName());
+            }
         }
         throw new Uncoercible(type().name(), targetClass.getCanonicalName());
     }
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/IntegerValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/IntegerValueTest.java
index 6403b602f3..b071fd67f2 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/value/IntegerValueTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/IntegerValueTest.java
@@ -148,5 +148,7 @@ void shouldMapToType() {
         assertEquals(expected, value.as(Double.class));
         assertEquals((float) expected, value.as(float.class));
         assertEquals((float) expected, value.as(Float.class));
+        assertEquals((short) expected, value.as(short.class));
+        assertEquals((short) expected, value.as(Short.class));
     }
 }
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/ListValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/ListValueTest.java
index 97e5be2e80..2b528bfd45 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/value/ListValueTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/ListValueTest.java
@@ -18,9 +18,12 @@
 
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.neo4j.driver.Values.value;
 
+import java.time.LocalDateTime;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import org.junit.jupiter.api.Test;
@@ -53,6 +56,107 @@ void shouldMapToType() {
         assertEquals(list, values.as(Object.class));
     }
 
+    @Test
+    void shouldMapCharValuesToArray() {
+        var array = new char[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+        var values = Values.value(array);
+        assertArrayEquals(array, values.as(char[].class));
+    }
+
+    @Test
+    void shouldMapBooleanValuesToArray() {
+        var array = new boolean[] {false};
+        var values = Values.value(array);
+        assertArrayEquals(array, values.as(boolean[].class));
+    }
+
+    @Test
+    void shouldMapStringValuesToArray() {
+        var array = new String[] {"value"};
+        var values = Values.value(array);
+        assertArrayEquals(array, values.as(String[].class));
+    }
+
+    @Test
+    void shouldMapLongValuesToArray() {
+        var array = new long[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+        var values = Values.value(array);
+        assertArrayEquals(array, values.as(long[].class));
+        assertArrayEquals(Arrays.stream(array).mapToInt(l -> (int) l).toArray(), values.as(int[].class));
+        assertArrayEquals(Arrays.stream(array).mapToDouble(l -> (double) l).toArray(), values.as(double[].class));
+        var expectedFloats = new float[array.length];
+        for (var i = 0; i < array.length; i++) expectedFloats[i] = (float) array[i];
+        assertArrayEquals(expectedFloats, values.as(float[].class));
+    }
+
+    @Test
+    void shouldMapIntegerValuesToArray() {
+        var array = new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+        var values = Values.value(array);
+        assertArrayEquals(array, values.as(int[].class));
+        assertArrayEquals(Arrays.stream(array).mapToLong(l -> (long) l).toArray(), values.as(long[].class));
+        assertArrayEquals(Arrays.stream(array).mapToDouble(l -> (double) l).toArray(), values.as(double[].class));
+        var expectedFloats = new float[array.length];
+        for (var i = 0; i < array.length; i++) expectedFloats[i] = (float) array[i];
+        assertArrayEquals(expectedFloats, values.as(float[].class));
+    }
+
+    @Test
+    void shouldMapShortValuesToArray() {
+        var array = new short[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+        var values = Values.value(array);
+        assertArrayEquals(array, values.as(short[].class));
+    }
+
+    @Test
+    void shouldMapDoubleValuesToArray() {
+        var array = new double[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+        var values = Values.value(array);
+        assertArrayEquals(array, values.as(double[].class));
+        assertArrayEquals(Arrays.stream(array).mapToLong(l -> (long) l).toArray(), values.as(long[].class));
+        assertArrayEquals(Arrays.stream(array).mapToInt(l -> (int) l).toArray(), values.as(int[].class));
+        var expectedFloats = new float[array.length];
+        for (var i = 0; i < array.length; i++) expectedFloats[i] = (float) array[i];
+        assertArrayEquals(expectedFloats, values.as(float[].class));
+    }
+
+    @Test
+    void shouldMapFloatValuesToArray() {
+        var array = new float[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+        var values = Values.value(array);
+        assertArrayEquals(array, values.as(float[].class));
+        var expectedDoubles = new double[array.length];
+        for (var i = 0; i < array.length; i++) expectedDoubles[i] = array[i];
+        assertArrayEquals(expectedDoubles, values.as(double[].class));
+        var expectedLongs = new long[array.length];
+        for (var i = 0; i < array.length; i++) expectedLongs[i] = (long) array[i];
+        assertArrayEquals(expectedLongs, values.as(long[].class));
+        var expectedIntegers = new int[array.length];
+        for (var i = 0; i < array.length; i++) expectedIntegers[i] = (int) array[i];
+        assertArrayEquals(expectedIntegers, values.as(int[].class));
+    }
+
+    @Test
+    void shouldMapObjectsToArray() {
+        var string = "value";
+        var localDateTime = LocalDateTime.now();
+        var array = new Object[] {string, localDateTime};
+        var values = Values.value(array);
+        assertArrayEquals(array, values.as(Object[].class));
+    }
+
+    @Test
+    void shouldMapMatrixValuesToArrays() {
+        var array = new long[10][10];
+        for (var i = 0; i < array.length; i++) {
+            for (var j = 0; j < 10; j++) {
+                array[i][j] = i * j;
+            }
+        }
+        var values = Values.value(array);
+        assertArrayEquals(array, values.as(long[][].class));
+    }
+
     private ListValue listValue(Value... values) {
         return new ListValue(values);
     }
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/StringValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/StringValueTest.java
index 7dd5df8ea3..24e9bbda2b 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/value/StringValueTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/StringValueTest.java
@@ -92,9 +92,11 @@ void shouldHaveStringType() {
 
     @Test
     void shouldMapToType() {
-        var string = "value";
+        var string = "0";
         var values = Values.value(string);
         assertEquals(string, values.as(String.class));
+        assertEquals(string.charAt(0), values.as(char.class));
+        assertEquals(string.charAt(0), values.as(Character.class));
         assertEquals(string, values.as(Serializable.class));
         assertEquals(string, values.as(Comparable.class));
         assertEquals(string, values.as(CharSequence.class));