targetClass) {
+ // for backwards compatibility only
+ throw new UnsupportedOperationException("not supported");
+ }
}
diff --git a/driver/src/main/java/org/neo4j/driver/Value.java b/driver/src/main/java/org/neo4j/driver/Value.java
index 88577f1f3d..6a7d872e51 100644
--- a/driver/src/main/java/org/neo4j/driver/Value.java
+++ b/driver/src/main/java/org/neo4j/driver/Value.java
@@ -30,6 +30,7 @@
import org.neo4j.driver.exceptions.value.LossyCoercion;
import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.value.NullValue;
+import org.neo4j.driver.mapping.Property;
import org.neo4j.driver.types.Entity;
import org.neo4j.driver.types.IsoDuration;
import org.neo4j.driver.types.MapAccessor;
@@ -41,6 +42,7 @@
import org.neo4j.driver.types.Type;
import org.neo4j.driver.types.TypeSystem;
import org.neo4j.driver.util.Immutable;
+import org.neo4j.driver.util.Preview;
/**
* A unit of data that adheres to the Neo4j type system.
@@ -52,7 +54,7 @@
* For example, a common String value should be tested for using isString
* and extracted using stringValue
.
* Navigating a tree structure
- *
+ *
* Because Neo4j often handles dynamic structures, this interface is designed to help
* you handle such structures in Java. Specifically, {@link Value} lets you navigate arbitrary tree
* structures without having to resort to type casting.
@@ -69,14 +71,14 @@
* }
* }
*
- *
+ *
* You can retrieve the name of the second user, John, like so:
*
* {@code
* String username = value.get("users").get(1).get("name").asString();
* }
*
- *
+ *
* You can also easily iterate over the users:
*
* {@code
@@ -87,6 +89,7 @@
* }
* }
*
+ *
* @since 1.0
*/
@Immutable
@@ -133,14 +136,16 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue {
*/
Value get(int index);
- /** @return The type of this value as defined in the Neo4j type system */
+ /**
+ * @return The type of this value as defined in the Neo4j type system
+ */
Type type();
/**
* Test if this value is a value of the given type
*
* @param type the given type
- * @return type.isTypeOf( this )
+ * @return type.isTypeOf(this)
*/
boolean hasType(Type type);
@@ -184,7 +189,7 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue {
* {@link TypeSystem#RELATIONSHIP()} - {@link Relationship}
* {@link TypeSystem#PATH()} - {@link Path}
*
- *
+ *
* Note that the types in {@link TypeSystem} refers to the Neo4j type system
* where {@link TypeSystem#INTEGER()} and {@link TypeSystem#FLOAT()} are both
* 64-bit precision. This is why these types return java {@link Long} and
@@ -197,9 +202,10 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue {
/**
* Apply the mapping function on the value if the value is not a {@link NullValue}, or the default value if the value is a {@link NullValue}.
- * @param mapper The mapping function defines how to map a {@link Value} to T.
+ *
+ * @param mapper The mapping function defines how to map a {@link Value} to T.
* @param defaultValue the value to return if the value is a {@link NullValue}
- * @param The return type
+ * @param The return type
* @return The value after applying the given mapping function or the default value if the value is {@link NullValue}.
*/
T computeOrDefault(Function mapper, T defaultValue);
@@ -218,21 +224,21 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue {
boolean asBoolean(boolean defaultValue);
/**
- * @return the value as a Java byte array, if possible.
- * @throws Uncoercible if value types are incompatible.
+ * @return the value as a Java byte array, if possible.
+ * @throws Uncoercible if value types are incompatible.
*/
byte[] asByteArray();
/**
- * @param defaultValue default to this value if the original value is a {@link NullValue}
- * @return the value as a Java byte array, if possible.
- * @throws Uncoercible if value types are incompatible.
+ * @param defaultValue default to this value if the original value is a {@link NullValue}
+ * @return the value as a Java byte array, if possible.
+ * @throws Uncoercible if value types are incompatible.
*/
byte[] asByteArray(byte[] defaultValue);
/**
- * @return the value as a Java String, if possible.
- * @throws Uncoercible if value types are incompatible.
+ * @return the value as a Java String, if possible.
+ * @throws Uncoercible if value types are incompatible.
*/
String asString();
@@ -254,16 +260,17 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue {
*
* @return the value as a Java long.
* @throws LossyCoercion if it is not possible to convert the value without loosing precision.
- * @throws Uncoercible if value types are incompatible.
+ * @throws Uncoercible if value types are incompatible.
*/
long asLong();
/**
* Returns a Java long if no precision is lost in the conversion.
+ *
* @param defaultValue return this default value if the value is a {@link NullValue}.
* @return the value as a Java long.
* @throws LossyCoercion if it is not possible to convert the value without loosing precision.
- * @throws Uncoercible if value types are incompatible.
+ * @throws Uncoercible if value types are incompatible.
*/
long asLong(long defaultValue);
@@ -272,16 +279,17 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue {
*
* @return the value as a Java int.
* @throws LossyCoercion if it is not possible to convert the value without loosing precision.
- * @throws Uncoercible if value types are incompatible.
+ * @throws Uncoercible if value types are incompatible.
*/
int asInt();
/**
* Returns a Java int if no precision is lost in the conversion.
+ *
* @param defaultValue return this default value if the value is a {@link NullValue}.
* @return the value as a Java int.
* @throws LossyCoercion if it is not possible to convert the value without loosing precision.
- * @throws Uncoercible if value types are incompatible.
+ * @throws Uncoercible if value types are incompatible.
*/
int asInt(int defaultValue);
@@ -290,16 +298,17 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue {
*
* @return the value as a Java double.
* @throws LossyCoercion if it is not possible to convert the value without loosing precision.
- * @throws Uncoercible if value types are incompatible.
+ * @throws Uncoercible if value types are incompatible.
*/
double asDouble();
/**
* Returns a Java double if no precision is lost in the conversion.
+ *
* @param defaultValue default to this value if the value is a {@link NullValue}.
* @return the value as a Java double.
* @throws LossyCoercion if it is not possible to convert the value without loosing precision.
- * @throws Uncoercible if value types are incompatible.
+ * @throws Uncoercible if value types are incompatible.
*/
double asDouble(double defaultValue);
@@ -308,16 +317,17 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue {
*
* @return the value as a Java float.
* @throws LossyCoercion if it is not possible to convert the value without loosing precision.
- * @throws Uncoercible if value types are incompatible.
+ * @throws Uncoercible if value types are incompatible.
*/
float asFloat();
/**
* Returns a Java float if no precision is lost in the conversion.
+ *
* @param defaultValue default to this value if the value is a {@link NullValue}
* @return the value as a Java float.
* @throws LossyCoercion if it is not possible to convert the value without loosing precision.
- * @throws Uncoercible if value types are incompatible.
+ * @throws Uncoercible if value types are incompatible.
*/
float asFloat(float defaultValue);
@@ -325,8 +335,8 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue {
* If the underlying type can be viewed as a list, returns a java list of
* values, where each value has been converted using {@link #asObject()}.
*
- * @see #asObject()
* @return the value as a Java list of values, if possible
+ * @see #asObject()
*/
List asList();
@@ -334,28 +344,28 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue {
* If the underlying type can be viewed as a list, returns a java list of
* values, where each value has been converted using {@link #asObject()}.
*
- * @see #asObject()
* @param defaultValue default to this value if the value is a {@link NullValue}
* @return the value as a Java list of values, if possible
+ * @see #asObject()
*/
List asList(List defaultValue);
/**
* @param mapFunction a function to map from Value to T. See {@link Values} for some predefined functions, such
- * as {@link Values#ofBoolean()}, {@link Values#ofList(Function)}.
- * @param the type of target list elements
- * @see Values for a long list of built-in conversion functions
+ * as {@link Values#ofBoolean()}, {@link Values#ofList(Function)}.
+ * @param the type of target list elements
* @return the value as a list of T obtained by mapping from the list elements, if possible
+ * @see Values for a long list of built-in conversion functions
*/
List asList(Function mapFunction);
/**
- * @param mapFunction a function to map from Value to T. See {@link Values} for some predefined functions, such
- * as {@link Values#ofBoolean()}, {@link Values#ofList(Function)}.
- * @param the type of target list elements
+ * @param mapFunction a function to map from Value to T. See {@link Values} for some predefined functions, such
+ * as {@link Values#ofBoolean()}, {@link Values#ofList(Function)}.
+ * @param the type of target list elements
* @param defaultValue default to this value if the value is a {@link NullValue}
- * @see Values for a long list of built-in conversion functions
* @return the value as a list of T obtained by mapping from the list elements, if possible
+ * @see Values for a long list of built-in conversion functions
*/
List asList(Function mapFunction, List defaultValue);
@@ -409,14 +419,14 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue {
/**
* @return the value as a {@link java.time.OffsetDateTime}, if possible.
- * @throws Uncoercible if value types are incompatible.
+ * @throws Uncoercible if value types are incompatible.
* @throws DateTimeException if zone information supplied by server is not supported by driver runtime.
*/
OffsetDateTime asOffsetDateTime();
/**
* @return the value as a {@link ZonedDateTime}, if possible.
- * @throws Uncoercible if value types are incompatible.
+ * @throws Uncoercible if value types are incompatible.
* @throws DateTimeException if zone information supplied by server is not supported by driver runtime.
*/
ZonedDateTime asZonedDateTime();
@@ -501,15 +511,217 @@ public interface Value extends MapAccessor, MapAccessorWithDefaultValue {
Map asMap(Map defaultValue);
/**
- * @param mapFunction a function to map from Value to T. See {@link Values} for some predefined functions, such
- * as {@link Values#ofBoolean()}, {@link Values#ofList(Function)}.
- * @param the type of map values
+ * @param mapFunction a function to map from Value to T. See {@link Values} for some predefined functions, such
+ * as {@link Values#ofBoolean()}, {@link Values#ofList(Function)}.
+ * @param the type of map values
* @param defaultValue default to this value if the value is a {@link NullValue}
- * @see Values for a long list of built-in conversion functions
* @return the value as a map from string keys to values of type T obtained from mapping he original map values, if possible
+ * @see Values for a long list of built-in conversion functions
*/
Map asMap(Function mapFunction, Map defaultValue);
+ /**
+ * Maps this value to the given type providing that is it supported.
+ *
+ * Basic Mapping
+ *
+ * Supported destination types depend on the value {@link Type}, please see the table below for more details.
+ *
+ * Supported Mappings
+ *
+ *
+ * Value Type
+ * Supported Target Types
+ *
+ *
+ *
+ *
+ * {@link TypeSystem#BOOLEAN}
+ * {@code boolean}, {@link Boolean}
+ *
+ *
+ * {@link TypeSystem#BYTES}
+ * {@code byte[]}
+ *
+ *
+ * {@link TypeSystem#STRING}
+ * {@link String}
+ *
+ *
+ * {@link TypeSystem#INTEGER}
+ * {@code long}, {@link Long}, {@code int}, {@link Integer}, {@code double}, {@link Double}, {@code float}, {@link Float}
+ *
+ *
+ * {@link TypeSystem#FLOAT}
+ * {@code long}, {@link Long}, {@code int}, {@link Integer}, {@code double}, {@link Double}, {@code float}, {@link Float}
+ *
+ *
+ * {@link TypeSystem#PATH}
+ * {@link Path}
+ *
+ *
+ * {@link TypeSystem#POINT}
+ * {@link Point}
+ *
+ *
+ * {@link TypeSystem#DATE}
+ * {@link LocalDate}
+ *
+ *
+ * {@link TypeSystem#TIME}
+ * {@link OffsetTime}
+ *
+ *
+ * {@link TypeSystem#LOCAL_TIME}
+ * {@link LocalTime}
+ *
+ *
+ * {@link TypeSystem#LOCAL_DATE_TIME}
+ * {@link LocalDateTime}
+ *
+ *
+ * {@link TypeSystem#DATE_TIME}
+ * {@link ZonedDateTime}, {@link OffsetDateTime}
+ *
+ *
+ * {@link TypeSystem#DURATION}
+ * {@link IsoDuration}, {@link java.time.Period} (only when {@code seconds = 0} and {@code nanoseconds = 0} and no overflow happens),
+ * {@link java.time.Duration} (only when {@code months = 0} and {@code days = 0} and no overflow happens)
+ *
+ *
+ * {@link TypeSystem#NULL}
+ * {@code null}
+ *
+ *
+ * {@link TypeSystem#LIST}
+ * {@link List}
+ *
+ *
+ * {@link TypeSystem#MAP}
+ * {@link Map}
+ *
+ *
+ * {@link TypeSystem#NODE}
+ * {@link Node}
+ *
+ *
+ * {@link TypeSystem#RELATIONSHIP}
+ * {@link Relationship}
+ *
+ *
+ *
+ *
+ *
+ * Object Mapping
+ *
+ * Mapping of user-defined properties to user-defined types is supported for the following value types:
+ *
+ * {@link TypeSystem#NODE}
+ * {@link TypeSystem#RELATIONSHIP}
+ * {@link TypeSystem#MAP}
+ *
+ *
+ * Example (using the Neo4j Movies Database ):
+ *
+ * {@code
+ * // assuming the following Java record
+ * public record Movie(String title, String tagline, long released) {}
+ * // the nodes may be mapped to Movie instances
+ * var movies = driver.executableQuery("MATCH (movie:Movie) RETURN movie")
+ * .execute()
+ * .records()
+ * .stream()
+ * .map(record -> record.get("movie").as(Movie.class))
+ * .toList();
+ * }
+ *
+ *
+ * Note that Object Mapping is an alternative to accessing the user-defined values in a {@link MapAccessor}.
+ * If Object Graph Mapping (OGM) is needed, please use a higher level solution built on top of the driver, like
+ * Spring Data Neo4j .
+ *
+ * The mapping is done by matching user-defined property names to target type constructor parameters. Therefore, the
+ * constructor parameters must either have {@link Property} annotation or have a matching name that is available
+ * at runtime (note that the constructor parameter names are typically changed by the compiler unless either the
+ * compiler {@code -parameters} option is used or they belong to the cannonical constructor of
+ * {@link java.lang.Record java.lang.Record}). The name matching is case-sensitive.
+ *
+ * Additionally, the {@link Property} annotation may be used when mapping a property with a different name to
+ * {@link java.lang.Record java.lang.Record} cannonical constructor parameter.
+ *
+ * The constructor selection criteria is the following (top priority first):
+ *
+ * Maximum matching properties.
+ * Minimum mismatching properties.
+ *
+ * The constructor search is done in the order defined by the {@link Class#getDeclaredConstructors} and is
+ * finished either when a full match is found with no mismatches or once all constructors have been visited.
+ *
+ * At least 1 property match must be present for mapping to work.
+ *
+ * A {@code null} value is used for arguments that don't have a matching property. If the argument does not accept
+ * {@code null} value (this includes primitive types), an alternative constructor that excludes it must be
+ * available.
+ *
+ * Example with optional property (using the Neo4j Movies Database ):
+ *
+ * {@code
+ * // assuming the following Java record
+ * public record Person(String name, long born) {
+ * // alternative constructor for values that don't have 'born' property available
+ * public Person(@Property("name") String name) {
+ * this(name, -1);
+ * }
+ * }
+ * // the nodes may be mapped to Person instances
+ * var persons = driver.executableQuery("MATCH (person:Person) RETURN person")
+ * .execute()
+ * .records()
+ * .stream()
+ * .map(record -> record.get("person").as(Person.class))
+ * .toList();
+ * }
+ *
+ *
+ * Types with generic parameters defined at the class level are not supported. However, constructor arguments with
+ * specific types are permitted.
+ *
+ * Example (using the Neo4j Movies Database ):
+ *
+ * {@code
+ * // assuming the following Java record
+ * public record Acted(List roles) {}
+ * // the relationships may be mapped to Acted instances
+ * var actedList = driver.executableQuery("MATCH ()-[acted:ACTED_IN]-() RETURN acted")
+ * .execute()
+ * .records()
+ * .stream()
+ * .map(record -> record.get("acted").as(Acted.class))
+ * .toList();
+ * }
+ *
+ * On the contrary, the following record would not be mapped because the type information is insufficient:
+ *
+ * {@code
+ * public record Acted(List roles) {}
+ * }
+ *
+ * Wildcard type value is not supported.
+ *
+ * @param targetClass the target class to map to
+ * @param the target type to map to
+ * @return the mapped value
+ * @throws Uncoercible when mapping to the target type is not possible
+ * @throws LossyCoercion when mapping cannot be achieved without losing precision
+ * @see Property
+ * @since 5.28.5
+ */
+ @Preview(name = "Object mapping")
+ default T as(Class targetClass) {
+ // for backwards compatibility only
+ throw new UnsupportedOperationException("not supported");
+ }
+
@Override
boolean equals(Object other);
diff --git a/driver/src/main/java/org/neo4j/driver/exceptions/value/ValueException.java b/driver/src/main/java/org/neo4j/driver/exceptions/value/ValueException.java
index edbd0a6bd3..244dcddd42 100644
--- a/driver/src/main/java/org/neo4j/driver/exceptions/value/ValueException.java
+++ b/driver/src/main/java/org/neo4j/driver/exceptions/value/ValueException.java
@@ -19,6 +19,7 @@
import java.io.Serial;
import org.neo4j.driver.exceptions.ClientException;
import org.neo4j.driver.internal.GqlStatusError;
+import org.neo4j.driver.util.Preview;
/**
* A ValueException indicates that the client has carried out an operation on values incorrectly.
@@ -33,12 +34,23 @@ public class ValueException extends ClientException {
* @param message the message
*/
public ValueException(String message) {
+ this(message, null);
+ }
+
+ /**
+ * Creates a new instance.
+ * @param message the message
+ * @param cause the cause
+ * @since 5.28.5
+ */
+ @Preview(name = "Object mapping")
+ public ValueException(String message, Throwable cause) {
super(
GqlStatusError.UNKNOWN.getStatus(),
GqlStatusError.UNKNOWN.getStatusDescription(message),
"N/A",
message,
GqlStatusError.DIAGNOSTIC_RECORD,
- null);
+ cause);
}
}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/InternalRecord.java b/driver/src/main/java/org/neo4j/driver/internal/InternalRecord.java
index 906eae8a30..954c09fb06 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/InternalRecord.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/InternalRecord.java
@@ -30,9 +30,11 @@
import org.neo4j.driver.Record;
import org.neo4j.driver.Value;
import org.neo4j.driver.Values;
+import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.types.InternalMapAccessorWithDefaultValue;
import org.neo4j.driver.internal.util.Extract;
import org.neo4j.driver.internal.util.QueryKeys;
+import org.neo4j.driver.internal.value.mapping.MapAccessorMapperProvider;
import org.neo4j.driver.util.Pair;
public class InternalRecord extends InternalMapAccessorWithDefaultValue implements Record {
@@ -116,6 +118,16 @@ public Map asMap(Function mapper) {
return Extract.map(this, mapper);
}
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.isAssignableFrom(Record.class)) {
+ return targetClass.cast(this);
+ }
+ return MapAccessorMapperProvider.mapper(this, targetClass)
+ .map(mapper -> mapper.map(this, targetClass))
+ .orElseThrow(() -> new Uncoercible(getClass().getCanonicalName(), targetClass.getCanonicalName()));
+ }
+
@Override
public String toString() {
return format("Record<%s>", formatPairs(asMap(ofValue())));
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/BooleanValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/BooleanValue.java
index 96e4ee0eb4..2671c66df5 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/BooleanValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/BooleanValue.java
@@ -16,6 +16,7 @@
*/
package org.neo4j.driver.internal.value;
+import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.types.Type;
@@ -57,6 +58,17 @@ public boolean asBoolean() {
return true;
}
+ @SuppressWarnings("unchecked")
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.isAssignableFrom(Boolean.class)) {
+ return targetClass.cast(Boolean.TRUE);
+ } else if (targetClass.isAssignableFrom(boolean.class)) {
+ return (T) Boolean.TRUE;
+ }
+ throw new Uncoercible(type().name(), targetClass.getCanonicalName());
+ }
+
@Override
public boolean isTrue() {
return true;
@@ -90,6 +102,17 @@ public boolean asBoolean() {
return false;
}
+ @SuppressWarnings("unchecked")
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.isAssignableFrom(Boolean.class)) {
+ return targetClass.cast(Boolean.FALSE);
+ } else if (targetClass.isAssignableFrom(boolean.class)) {
+ return (T) Boolean.FALSE;
+ }
+ throw new Uncoercible(type().name(), targetClass.getCanonicalName());
+ }
+
@Override
public boolean isFalse() {
return true;
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/BytesValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/BytesValue.java
index 7eff49a6d6..c8ad70a580 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/BytesValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/BytesValue.java
@@ -17,6 +17,7 @@
package org.neo4j.driver.internal.value;
import java.util.Arrays;
+import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.types.Type;
@@ -50,6 +51,14 @@ public byte[] asByteArray() {
return val;
}
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.isAssignableFrom(byte[].class)) {
+ return targetClass.cast(asByteArray());
+ }
+ throw new Uncoercible(type().name(), targetClass.getCanonicalName());
+ }
+
@Override
public Type type() {
return InternalTypeSystem.TYPE_SYSTEM.BYTES();
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/DateTimeValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/DateTimeValue.java
index d34c46201b..e2c0ca0164 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/DateTimeValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/DateTimeValue.java
@@ -18,6 +18,7 @@
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
+import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.types.Type;
@@ -45,4 +46,14 @@ public Type type() {
public BoltValue asBoltValue() {
return new BoltValue(this, org.neo4j.bolt.connection.values.Type.DATE_TIME);
}
+
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.isAssignableFrom(ZonedDateTime.class)) {
+ return targetClass.cast(asZonedDateTime());
+ } else if (targetClass.isAssignableFrom(OffsetDateTime.class)) {
+ return targetClass.cast(asOffsetDateTime());
+ }
+ throw new Uncoercible(type().name(), targetClass.getCanonicalName());
+ }
}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/DateValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/DateValue.java
index 8684ea7bff..07ab3415e9 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/DateValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/DateValue.java
@@ -17,6 +17,7 @@
package org.neo4j.driver.internal.value;
import java.time.LocalDate;
+import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.types.Type;
@@ -39,4 +40,12 @@ public Type type() {
public BoltValue asBoltValue() {
return new BoltValue(this, org.neo4j.bolt.connection.values.Type.DATE);
}
+
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.isAssignableFrom(LocalDate.class)) {
+ return targetClass.cast(asLocalDate());
+ }
+ throw new Uncoercible(type().name(), targetClass.getCanonicalName());
+ }
}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/DurationValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/DurationValue.java
index f20fa193b0..cf37b54d13 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/DurationValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/DurationValue.java
@@ -39,4 +39,12 @@ public Type type() {
public BoltValue asBoltValue() {
return new BoltValue(this, org.neo4j.bolt.connection.values.Type.DURATION);
}
+
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.isAssignableFrom(IsoDuration.class)) {
+ return targetClass.cast(asIsoDuration());
+ }
+ return asMapped(targetClass);
+ }
}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/FloatValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/FloatValue.java
index c8856b9d79..df6771ed5e 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/FloatValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/FloatValue.java
@@ -17,6 +17,7 @@
package org.neo4j.driver.internal.value;
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;
@@ -72,6 +73,29 @@ public float asFloat() {
return floatVal;
}
+ @SuppressWarnings("unchecked")
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.equals(double.class)) {
+ return (T) Double.valueOf(asDouble());
+ } else if (targetClass.isAssignableFrom(Double.class)) {
+ return targetClass.cast(asDouble());
+ } else if (targetClass.equals(long.class)) {
+ return (T) Long.valueOf(asLong());
+ } else if (targetClass.equals(Long.class)) {
+ return targetClass.cast(asLong());
+ } else if (targetClass.equals(int.class)) {
+ return (T) Integer.valueOf(asInt());
+ } else if (targetClass.equals(Integer.class)) {
+ return targetClass.cast(asInt());
+ } else if (targetClass.equals(float.class)) {
+ return (T) Float.valueOf(asFloat());
+ } else if (targetClass.equals(Float.class)) {
+ return targetClass.cast(asFloat());
+ }
+ throw new Uncoercible(type().name(), targetClass.getCanonicalName());
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
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 6631b2c096..0211135a09 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
@@ -17,6 +17,7 @@
package org.neo4j.driver.internal.value;
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;
@@ -65,6 +66,29 @@ public float asFloat() {
return (float) val;
}
+ @SuppressWarnings("unchecked")
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.equals(long.class)) {
+ return (T) Long.valueOf(asLong());
+ } else if (targetClass.isAssignableFrom(Long.class)) {
+ return targetClass.cast(asLong());
+ } else if (targetClass.equals(int.class)) {
+ return (T) Integer.valueOf(asInt());
+ } else if (targetClass.equals(Integer.class)) {
+ return targetClass.cast(asInt());
+ } else if (targetClass.equals(double.class)) {
+ return (T) Double.valueOf(asDouble());
+ } else if (targetClass.equals(Double.class)) {
+ return targetClass.cast(asDouble());
+ } else if (targetClass.equals(float.class)) {
+ return (T) Float.valueOf(asFloat());
+ } else if (targetClass.equals(Float.class)) {
+ return targetClass.cast(asFloat());
+ }
+ throw new Uncoercible(type().name(), targetClass.getCanonicalName());
+ }
+
@Override
public String toString() {
return Long.toString(val);
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/InternalValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/InternalValue.java
index b487f45257..f41927dc24 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/InternalValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/InternalValue.java
@@ -16,7 +16,10 @@
*/
package org.neo4j.driver.internal.value;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
import org.neo4j.driver.Value;
+import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.AsValue;
import org.neo4j.driver.internal.types.TypeConstructor;
@@ -24,4 +27,18 @@ public interface InternalValue extends Value, AsValue {
TypeConstructor typeConstructor();
BoltValue asBoltValue();
+
+ default Object as(Type type) {
+ if (type instanceof ParameterizedType parameterizedType) {
+ return as(parameterizedType);
+ } else if (type instanceof Class> classType) {
+ return as(classType);
+ } else {
+ throw new Uncoercible(type().name(), type.toString());
+ }
+ }
+
+ default Object as(ParameterizedType type) {
+ throw new Uncoercible(type().name(), type.toString());
+ }
}
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 e0ce7dce0b..acabe97626 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,12 +18,14 @@
import static org.neo4j.driver.Values.ofObject;
+import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import org.neo4j.driver.Value;
import org.neo4j.driver.Values;
+import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.internal.util.Extract;
import org.neo4j.driver.types.Type;
@@ -58,6 +60,29 @@ public List asList(Function mapFunction) {
return Extract.list(values, mapFunction);
}
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.isAssignableFrom(List.class)) {
+ return targetClass.cast(asList());
+ }
+ throw new Uncoercible(type().name(), targetClass.getCanonicalName());
+ }
+
+ @Override
+ public Object as(ParameterizedType type) {
+ var rawType = type.getRawType();
+ if (rawType instanceof Class> cls) {
+ if (cls.isAssignableFrom(List.class)) {
+ return asList(v -> {
+ var value = (InternalValue) v;
+ var typeArgument = type.getActualTypeArguments()[0];
+ return value.as(typeArgument);
+ });
+ }
+ }
+ throw new Uncoercible(type().name(), type.toString());
+ }
+
@Override
public int size() {
return values.length;
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/LocalDateTimeValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/LocalDateTimeValue.java
index 24848279a9..90d670a316 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/LocalDateTimeValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/LocalDateTimeValue.java
@@ -17,6 +17,7 @@
package org.neo4j.driver.internal.value;
import java.time.LocalDateTime;
+import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.types.Type;
@@ -39,4 +40,12 @@ public Type type() {
public BoltValue asBoltValue() {
return new BoltValue(this, org.neo4j.bolt.connection.values.Type.LOCAL_DATE_TIME);
}
+
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.isAssignableFrom(LocalDateTime.class)) {
+ return targetClass.cast(asLocalDateTime());
+ }
+ throw new Uncoercible(type().name(), targetClass.getCanonicalName());
+ }
}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/LocalTimeValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/LocalTimeValue.java
index c389410168..1b275a24c8 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/LocalTimeValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/LocalTimeValue.java
@@ -17,6 +17,7 @@
package org.neo4j.driver.internal.value;
import java.time.LocalTime;
+import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.types.Type;
@@ -39,4 +40,12 @@ public Type type() {
public BoltValue asBoltValue() {
return new BoltValue(this, org.neo4j.bolt.connection.values.Type.LOCAL_TIME);
}
+
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.isAssignableFrom(LocalTime.class)) {
+ return targetClass.cast(asLocalTime());
+ }
+ throw new Uncoercible(type().name(), targetClass.getCanonicalName());
+ }
}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/MapValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/MapValue.java
index d1d1cab1a2..c58e77c257 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/MapValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/MapValue.java
@@ -20,10 +20,12 @@
import static org.neo4j.driver.Values.ofValue;
import static org.neo4j.driver.internal.util.Format.formatPairs;
+import java.lang.reflect.ParameterizedType;
import java.util.Map;
import java.util.function.Function;
import org.neo4j.driver.Value;
import org.neo4j.driver.Values;
+import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.internal.util.Extract;
import org.neo4j.driver.types.Type;
@@ -48,6 +50,34 @@ public Map asObject() {
return asMap(ofObject());
}
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.isAssignableFrom(Map.class)) {
+ return targetClass.cast(asMap());
+ }
+ return asMapped(targetClass);
+ }
+
+ @Override
+ public Object as(ParameterizedType type) {
+ var rawType = type.getRawType();
+ if (rawType instanceof Class> cls) {
+ if (cls.isAssignableFrom(Map.class)) {
+ var keyType = type.getActualTypeArguments()[0];
+ if (keyType instanceof Class> keyCls) {
+ if (keyCls.isAssignableFrom(String.class)) {
+ var valueType = type.getActualTypeArguments()[1];
+ return asMap(v -> {
+ var value = (InternalValue) v;
+ return value.as(valueType);
+ });
+ }
+ }
+ }
+ }
+ throw new Uncoercible(type().name(), type.toString());
+ }
+
@Override
public Map asMap() {
return Extract.map(val, ofObject());
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/NodeValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/NodeValue.java
index 1615378df6..d859db9ddd 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/NodeValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/NodeValue.java
@@ -39,4 +39,12 @@ public Type type() {
public BoltValue asBoltValue() {
return new BoltValue(this, org.neo4j.bolt.connection.values.Type.NODE);
}
+
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.isAssignableFrom(Node.class)) {
+ return targetClass.cast(asNode());
+ }
+ return asMapped(targetClass);
+ }
}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/NullValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/NullValue.java
index 4ec87a39fd..9ad122f4a0 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/NullValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/NullValue.java
@@ -16,6 +16,7 @@
*/
package org.neo4j.driver.internal.value;
+import java.lang.reflect.ParameterizedType;
import org.neo4j.driver.Value;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.types.Type;
@@ -40,6 +41,16 @@ public String asString() {
return "null";
}
+ @Override
+ public T as(Class targetClass) {
+ return null;
+ }
+
+ @Override
+ public Object as(ParameterizedType type) {
+ return null;
+ }
+
@Override
public Type type() {
return InternalTypeSystem.TYPE_SYSTEM.NULL();
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/PathValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/PathValue.java
index f7f4037b86..36d0f84996 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/PathValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/PathValue.java
@@ -44,4 +44,12 @@ public Type type() {
public BoltValue asBoltValue() {
return new BoltValue(this, org.neo4j.bolt.connection.values.Type.PATH);
}
+
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.isAssignableFrom(Path.class)) {
+ return targetClass.cast(asPath());
+ }
+ return asMapped(targetClass);
+ }
}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/PointValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/PointValue.java
index 172376a6f3..feb8ac3e69 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/PointValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/PointValue.java
@@ -16,6 +16,7 @@
*/
package org.neo4j.driver.internal.value;
+import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.types.Point;
import org.neo4j.driver.types.Type;
@@ -39,4 +40,12 @@ public Type type() {
public BoltValue asBoltValue() {
return new BoltValue(this, org.neo4j.bolt.connection.values.Type.POINT);
}
+
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.isAssignableFrom(Point.class)) {
+ return targetClass.cast(asPoint());
+ }
+ throw new Uncoercible(type().name(), targetClass.getCanonicalName());
+ }
}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/RelationshipValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/RelationshipValue.java
index 19683e178c..84dead0f11 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/RelationshipValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/RelationshipValue.java
@@ -39,4 +39,12 @@ public Type type() {
public BoltValue asBoltValue() {
return new BoltValue(this, org.neo4j.bolt.connection.values.Type.RELATIONSHIP);
}
+
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.isAssignableFrom(Relationship.class)) {
+ return targetClass.cast(asRelationship());
+ }
+ return asMapped(targetClass);
+ }
}
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 7d38eeda4f..75c92f03f7 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.Uncoercible;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.types.Type;
@@ -50,6 +51,14 @@ public String asString() {
return val;
}
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.isAssignableFrom(String.class)) {
+ return targetClass.cast(asString());
+ }
+ throw new Uncoercible(type().name(), targetClass.getCanonicalName());
+ }
+
@Override
public String toString() {
return String.format("\"%s\"", val.replace("\"", "\\\""));
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/TimeValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/TimeValue.java
index c74529f444..0cbe2cb944 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/TimeValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/TimeValue.java
@@ -17,6 +17,7 @@
package org.neo4j.driver.internal.value;
import java.time.OffsetTime;
+import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.types.Type;
@@ -39,4 +40,12 @@ public Type type() {
public BoltValue asBoltValue() {
return new BoltValue(this, org.neo4j.bolt.connection.values.Type.TIME);
}
+
+ @Override
+ public T as(Class targetClass) {
+ if (targetClass.isAssignableFrom(OffsetTime.class)) {
+ return targetClass.cast(asOffsetTime());
+ }
+ throw new Uncoercible(type().name(), targetClass.getCanonicalName());
+ }
}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/UnsupportedDateTimeValue.java b/driver/src/main/java/org/neo4j/driver/internal/value/UnsupportedDateTimeValue.java
index 27e31404c8..d04068b325 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/UnsupportedDateTimeValue.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/UnsupportedDateTimeValue.java
@@ -20,6 +20,7 @@
import java.time.DateTimeException;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
+import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.types.Type;
@@ -40,6 +41,11 @@ public ZonedDateTime asZonedDateTime() {
throw instantiateDateTimeException();
}
+ @Override
+ public T as(Class targetClass) {
+ throw new Uncoercible(type().name(), targetClass.getCanonicalName());
+ }
+
@Override
public Object asObject() {
throw instantiateDateTimeException();
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java b/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java
index 3c9a0e3ca6..24d875518d 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java
@@ -36,6 +36,7 @@
import org.neo4j.driver.internal.types.InternalMapAccessorWithDefaultValue;
import org.neo4j.driver.internal.types.TypeConstructor;
import org.neo4j.driver.internal.types.TypeRepresentation;
+import org.neo4j.driver.internal.value.mapping.MapAccessorMapperProvider;
import org.neo4j.driver.types.Entity;
import org.neo4j.driver.types.IsoDuration;
import org.neo4j.driver.types.Node;
@@ -343,6 +344,12 @@ public final TypeConstructor typeConstructor() {
return ((TypeRepresentation) type()).constructor();
}
+ protected T asMapped(Class targetClass) {
+ return MapAccessorMapperProvider.mapper(this, targetClass)
+ .map(mapper -> mapper.map(this, targetClass))
+ .orElseThrow(() -> new Uncoercible(type().name(), targetClass.getCanonicalName()));
+ }
+
// Force implementation
@Override
public abstract boolean equals(Object obj);
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/mapping/Argument.java b/driver/src/main/java/org/neo4j/driver/internal/value/mapping/Argument.java
new file mode 100644
index 0000000000..683ab875af
--- /dev/null
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/mapping/Argument.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.neo4j.driver.internal.value.mapping;
+
+import java.lang.reflect.Type;
+import org.neo4j.driver.internal.value.InternalValue;
+
+record Argument(String propertyName, Type type, InternalValue value) {}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/mapping/ConstructorFinder.java b/driver/src/main/java/org/neo4j/driver/internal/value/mapping/ConstructorFinder.java
new file mode 100644
index 0000000000..53b6e60204
--- /dev/null
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/mapping/ConstructorFinder.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.neo4j.driver.internal.value.mapping;
+
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import org.neo4j.driver.Values;
+import org.neo4j.driver.internal.value.InternalValue;
+import org.neo4j.driver.mapping.Property;
+import org.neo4j.driver.types.MapAccessor;
+
+class ConstructorFinder {
+ @SuppressWarnings("unchecked")
+ public Optional> findConstructor(MapAccessor mapAccessor, Class targetClass) {
+ PropertiesMatch bestPropertiesMatch = null;
+ var constructors = targetClass.getDeclaredConstructors();
+ var propertyNamesSize = mapAccessor.size();
+ for (var constructor : constructors) {
+ var accessible = false;
+ try {
+ accessible = constructor.canAccess(null);
+ } catch (Throwable e) {
+ // ignored
+ }
+ if (!accessible) {
+ continue;
+ }
+ var matchNumbers = matchPropertyNames(mapAccessor, constructor);
+ if (bestPropertiesMatch == null
+ || (matchNumbers.match() >= bestPropertiesMatch.match()
+ && matchNumbers.mismatch() < bestPropertiesMatch.mismatch())) {
+ bestPropertiesMatch = (PropertiesMatch) matchNumbers;
+ if (bestPropertiesMatch.match() == propertyNamesSize && bestPropertiesMatch.mismatch() == 0) {
+ break;
+ }
+ }
+ }
+ if (bestPropertiesMatch == null || bestPropertiesMatch.match() == 0) {
+ return Optional.empty();
+ }
+
+ return Optional.of(new ObjectMetadata<>(bestPropertiesMatch.constructor(), bestPropertiesMatch.arguments()));
+ }
+
+ private PropertiesMatch matchPropertyNames(MapAccessor mapAccessor, Constructor constructor) {
+ var match = 0;
+ var mismatch = 0;
+ var parameters = constructor.getParameters();
+ var arguments = new ArrayList(parameters.length);
+ for (var parameter : parameters) {
+ var propertyNameAnnotation = parameter.getAnnotation(Property.class);
+ var propertyName = propertyNameAnnotation != null ? propertyNameAnnotation.value() : parameter.getName();
+ var value = mapAccessor.get(propertyName);
+ if (value != null) {
+ match++;
+ } else {
+ mismatch++;
+ }
+ arguments.add(new Argument(
+ propertyName,
+ parameter.getParameterizedType(),
+ value != null ? (InternalValue) value : (InternalValue) Values.NULL));
+ }
+ return new PropertiesMatch<>(match, mismatch, constructor, arguments);
+ }
+
+ private record PropertiesMatch(int match, int mismatch, Constructor constructor, List arguments) {}
+}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/mapping/DurationMapper.java b/driver/src/main/java/org/neo4j/driver/internal/value/mapping/DurationMapper.java
new file mode 100644
index 0000000000..d8a31a2faf
--- /dev/null
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/mapping/DurationMapper.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.neo4j.driver.internal.value.mapping;
+
+import java.time.Duration;
+import java.time.Period;
+import java.time.temporal.TemporalAmount;
+import org.neo4j.driver.Value;
+import org.neo4j.driver.exceptions.value.LossyCoercion;
+import org.neo4j.driver.exceptions.value.Uncoercible;
+import org.neo4j.driver.types.MapAccessor;
+import org.neo4j.driver.types.TypeSystem;
+
+class DurationMapper implements MapAccessorMapper {
+ private static final TypeSystem TS = TypeSystem.getDefault();
+
+ @Override
+ public boolean supports(MapAccessor mapAccessor, Class> targetClass) {
+ return (mapAccessor instanceof Value value && TS.DURATION().equals(value.type()))
+ && (targetClass.isAssignableFrom(Period.class) || targetClass.isAssignableFrom(Duration.class));
+ }
+
+ @Override
+ public TemporalAmount map(MapAccessor mapAccessor, Class targetClass) {
+ if (mapAccessor instanceof Value value) {
+ var isoDuration = value.asIsoDuration();
+ if (targetClass.isAssignableFrom(TemporalAmount.class)) {
+ return isoDuration;
+ } else if (targetClass.isAssignableFrom(Period.class)) {
+ if (isoDuration.seconds() != 0 || isoDuration.nanoseconds() != 0) {
+ throw new LossyCoercion(value.type().name(), targetClass.getCanonicalName());
+ }
+ var totalMonths = isoDuration.months();
+ var splitYears = totalMonths / 12;
+ var splitMonths = totalMonths % 12;
+ try {
+ return Period.of(
+ Math.toIntExact(splitYears),
+ Math.toIntExact(splitMonths),
+ Math.toIntExact(isoDuration.days()))
+ .normalized();
+ } catch (ArithmeticException e) {
+ throw new LossyCoercion(value.type().name(), targetClass.getCanonicalName());
+ }
+ } else if (targetClass.isAssignableFrom(Duration.class)) {
+ if (isoDuration.months() != 0 || isoDuration.days() != 0) {
+ throw new LossyCoercion(value.type().name(), targetClass.getCanonicalName());
+ }
+ return Duration.ofSeconds(isoDuration.seconds()).plusNanos(isoDuration.nanoseconds());
+ }
+ throw new Uncoercible(value.type().name(), targetClass.getCanonicalName());
+ } else {
+ throw new Uncoercible(mapAccessor.getClass().getCanonicalName(), targetClass.getCanonicalName());
+ }
+ }
+}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/mapping/MapAccessorMapper.java b/driver/src/main/java/org/neo4j/driver/internal/value/mapping/MapAccessorMapper.java
new file mode 100644
index 0000000000..cfc41b3bf0
--- /dev/null
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/mapping/MapAccessorMapper.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.neo4j.driver.internal.value.mapping;
+
+import org.neo4j.driver.types.MapAccessor;
+
+public interface MapAccessorMapper {
+
+ boolean supports(MapAccessor mapAccessor, Class> targetClass);
+
+ T map(MapAccessor mapAccessor, Class targetClass);
+}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/mapping/MapAccessorMapperProvider.java b/driver/src/main/java/org/neo4j/driver/internal/value/mapping/MapAccessorMapperProvider.java
new file mode 100644
index 0000000000..4cc947dd54
--- /dev/null
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/mapping/MapAccessorMapperProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.neo4j.driver.internal.value.mapping;
+
+import java.util.List;
+import java.util.Optional;
+import org.neo4j.driver.types.MapAccessor;
+
+public class MapAccessorMapperProvider {
+ private static final List extends MapAccessorMapper>> mapAccessorMappers =
+ List.of(new ObjectMapper<>(), new DurationMapper());
+
+ @SuppressWarnings("unchecked")
+ public static Optional extends MapAccessorMapper> mapper(MapAccessor mapAccessor, Class targetClass) {
+ return (Optional extends MapAccessorMapper>) mapAccessorMappers.stream()
+ .filter(mapper -> mapper.supports(mapAccessor, targetClass))
+ .findFirst();
+ }
+}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/mapping/ObjectInstantiator.java b/driver/src/main/java/org/neo4j/driver/internal/value/mapping/ObjectInstantiator.java
new file mode 100644
index 0000000000..2cfd7f18ba
--- /dev/null
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/mapping/ObjectInstantiator.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.neo4j.driver.internal.value.mapping;
+
+import java.util.List;
+import org.neo4j.driver.exceptions.value.ValueException;
+
+class ObjectInstantiator {
+
+ T instantiate(ObjectMetadata metadata) {
+ var constructor = metadata.constructor();
+ var targetTypeName = constructor.getDeclaringClass().getCanonicalName();
+ var initargs = initargs(targetTypeName, metadata.arguments());
+ try {
+ return constructor.newInstance(initargs);
+ } catch (Throwable e) {
+ throw new ValueException("Failed to instantiate '%s'".formatted(targetTypeName), e);
+ }
+ }
+
+ private Object[] initargs(String targetTypeName, List arguments) {
+ var initargs = new Object[arguments.size()];
+ for (var i = 0; i < initargs.length; i++) {
+ var argument = arguments.get(i);
+ var type = argument.type();
+ try {
+ initargs[i] = argument.value().as(type);
+ } catch (Throwable e) {
+ throw new ValueException(
+ "Failed to map '%s' property to '%s' for '%s' instantiation"
+ .formatted(argument.propertyName(), type.getTypeName(), targetTypeName),
+ e);
+ }
+ }
+ return initargs;
+ }
+}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/mapping/ObjectMapper.java b/driver/src/main/java/org/neo4j/driver/internal/value/mapping/ObjectMapper.java
new file mode 100644
index 0000000000..c18d413b62
--- /dev/null
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/mapping/ObjectMapper.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.neo4j.driver.internal.value.mapping;
+
+import java.util.Set;
+import org.neo4j.driver.Value;
+import org.neo4j.driver.exceptions.value.ValueException;
+import org.neo4j.driver.types.MapAccessor;
+import org.neo4j.driver.types.Type;
+import org.neo4j.driver.types.TypeSystem;
+
+class ObjectMapper implements MapAccessorMapper {
+ private static final TypeSystem TS = TypeSystem.getDefault();
+
+ private static final Set SUPPORTED_VALUE_TYPES = Set.of(TS.MAP(), TS.NODE(), TS.RELATIONSHIP());
+
+ private static final ConstructorFinder CONSTRUCTOR_FINDER = new ConstructorFinder();
+
+ private static final ObjectInstantiator OBJECT_INSTANTIATOR = new ObjectInstantiator();
+
+ @Override
+ public boolean supports(MapAccessor mapAccessor, Class> targetClass) {
+ return mapAccessor instanceof Value value
+ ? SUPPORTED_VALUE_TYPES.contains(value.type())
+ : mapAccessor instanceof org.neo4j.driver.Record;
+ }
+
+ @Override
+ public T map(MapAccessor mapAccessor, Class targetClass) {
+ return CONSTRUCTOR_FINDER
+ .findConstructor(mapAccessor, targetClass)
+ .map(OBJECT_INSTANTIATOR::instantiate)
+ .orElseThrow(() -> new ValueException(
+ "No suitable constructor has been found for '%s'".formatted(targetClass.getCanonicalName())));
+ }
+}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/value/mapping/ObjectMetadata.java b/driver/src/main/java/org/neo4j/driver/internal/value/mapping/ObjectMetadata.java
new file mode 100644
index 0000000000..9733464729
--- /dev/null
+++ b/driver/src/main/java/org/neo4j/driver/internal/value/mapping/ObjectMetadata.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.neo4j.driver.internal.value.mapping;
+
+import java.lang.reflect.Constructor;
+import java.util.List;
+
+record ObjectMetadata(Constructor constructor, List arguments) {}
diff --git a/driver/src/main/java/org/neo4j/driver/mapping/Property.java b/driver/src/main/java/org/neo4j/driver/mapping/Property.java
new file mode 100644
index 0000000000..78be893c0c
--- /dev/null
+++ b/driver/src/main/java/org/neo4j/driver/mapping/Property.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.neo4j.driver.mapping;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.neo4j.driver.util.Preview;
+
+/**
+ * Defines property name that should be mapped to the annotated parameter.
+ *
+ * @since 5.28.5
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+@Preview(name = "Object mapping")
+public @interface Property {
+ /**
+ * The property name that should be mapped to the annotated parameter.
+ *
+ * @return the property name
+ */
+ String value();
+}
diff --git a/driver/src/test/java/org/neo4j/driver/ObjectMappingTests.java b/driver/src/test/java/org/neo4j/driver/ObjectMappingTests.java
new file mode 100644
index 0000000000..889db48ad0
--- /dev/null
+++ b/driver/src/test/java/org/neo4j/driver/ObjectMappingTests.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.neo4j.driver;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.OffsetTime;
+import java.time.Period;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Named;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.neo4j.driver.internal.InternalIsoDuration;
+import org.neo4j.driver.internal.InternalNode;
+import org.neo4j.driver.internal.InternalPoint2D;
+import org.neo4j.driver.internal.InternalPoint3D;
+import org.neo4j.driver.internal.InternalRecord;
+import org.neo4j.driver.internal.InternalRelationship;
+import org.neo4j.driver.internal.value.NodeValue;
+import org.neo4j.driver.internal.value.RelationshipValue;
+import org.neo4j.driver.mapping.Property;
+import org.neo4j.driver.types.IsoDuration;
+import org.neo4j.driver.types.Point;
+
+class ObjectMappingTests {
+ @ParameterizedTest
+ @MethodSource("shouldMapValueArgs")
+ void shouldMapValue(Function, ValueHolder> valueFunction) {
+ // given
+ var string = "string";
+ var listWithString = List.of(string, string);
+ var listWithStringValueHolder = List.of(new StringValueHolder(string), new StringValueHolder(string));
+ var bytes = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ var bool = false;
+ var boltInteger = Long.MIN_VALUE;
+ var boltFloat = Double.MIN_VALUE;
+ var date = LocalDate.now();
+ var time = OffsetTime.now();
+ var dateTime = ZonedDateTime.now();
+ var localDateTime = LocalDateTime.now();
+ var duration = new InternalIsoDuration(Duration.ZERO);
+ var period = Period.ofYears(1000);
+ var javaDuration = Duration.of(1000, ChronoUnit.MINUTES);
+ var point2d = new InternalPoint2D(0, 0, 0);
+ var point3d = new InternalPoint3D(0, 0, 0, 0);
+
+ var properties = Map.ofEntries(
+ Map.entry("string", Values.value(string)),
+ Map.entry("nullValue", Values.value((Object) null)),
+ Map.entry("listWithString", Values.value(listWithString)),
+ // also verifies MapValue
+ Map.entry(
+ "listWithStringValueHolder",
+ Values.value(listWithStringValueHolder.stream()
+ .map(v -> Map.of("string", Values.value(v.string())))
+ .toList())),
+ Map.entry("bytes", Values.value(bytes)),
+ Map.entry("bool", Values.value(bool)),
+ Map.entry("boltInteger", Values.value(boltInteger)),
+ Map.entry("boltFloat", Values.value(boltFloat)),
+ Map.entry("date", Values.value(date)),
+ Map.entry("time", Values.value(time)),
+ Map.entry("dateTime", Values.value(dateTime)),
+ Map.entry("localDateTime", Values.value(localDateTime)),
+ Map.entry("duration", Values.value(duration)),
+ Map.entry("period", Values.value(period)),
+ Map.entry("javaDuration", Values.value(javaDuration)),
+ Map.entry("point2d", Values.value(point2d)),
+ Map.entry("point3d", Values.value(point3d)));
+
+ // when
+ var valueHolder = valueFunction.apply(properties);
+
+ // then
+ assertEquals(string, valueHolder.string());
+ assertNull(valueHolder.nullValue());
+ assertEquals(listWithString, valueHolder.listWithString());
+ assertEquals(listWithStringValueHolder, valueHolder.listWithStringValueHolder());
+ assertEquals(bytes, valueHolder.bytes());
+ assertEquals(bool, valueHolder.bool());
+ assertEquals(boltInteger, valueHolder.boltInteger());
+ assertEquals(boltFloat, valueHolder.boltFloat());
+ assertEquals(date, valueHolder.date());
+ assertEquals(time, valueHolder.time());
+ assertEquals(dateTime, valueHolder.dateTime());
+ assertEquals(localDateTime, valueHolder.localDateTime());
+ assertEquals(duration, valueHolder.duration());
+ assertEquals(period, valueHolder.period());
+ assertEquals(javaDuration, valueHolder.javaDuration());
+ assertEquals(point2d, valueHolder.point2d());
+ assertEquals(point3d, valueHolder.point3d());
+ }
+
+ static Stream shouldMapValueArgs() {
+ return Stream.of(
+ Arguments.of(Named., ValueHolder>>of(
+ "node", properties -> new NodeValue(new InternalNode(0L, Set.of("Product"), properties))
+ .as(ValueHolder.class))),
+ Arguments.of(Named., ValueHolder>>of(
+ "relationship",
+ properties -> new RelationshipValue(new InternalRelationship(0L, 0L, 0L, "Product", properties))
+ .as(ValueHolder.class))),
+ Arguments.of(Named., ValueHolder>>of(
+ "map", properties -> Values.value(properties).as(ValueHolder.class))),
+ Arguments.of(Named., ValueHolder>>of("record", properties -> {
+ var keys = new ArrayList();
+ var values = new Value[properties.size()];
+ var i = 0;
+ for (var entry : properties.entrySet()) {
+ keys.add(entry.getKey());
+ values[i] = entry.getValue();
+ i++;
+ }
+ return new InternalRecord(keys, values).as(ValueHolder.class);
+ })));
+ }
+
+ public record ValueHolder(
+ String string,
+ Object nullValue,
+ List listWithString,
+ List listWithStringValueHolder,
+ byte[] bytes,
+ boolean bool,
+ long boltInteger,
+ double boltFloat,
+ LocalDate date,
+ OffsetTime time,
+ ZonedDateTime dateTime,
+ LocalDateTime localDateTime,
+ IsoDuration duration,
+ Period period,
+ Duration javaDuration,
+ Point point2d,
+ Point point3d) {}
+
+ public record StringValueHolder(String string) {}
+
+ @Test
+ void shouldUseConstructorWithMaxMatchesAndMinMismatches() {
+ // given
+ var string = "string";
+ var bytes = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ var bool = false;
+
+ var properties = Map.ofEntries(
+ Map.entry("string", Values.value(string)),
+ Map.entry("bytes", Values.value(bytes)),
+ Map.entry("bool", Values.value(bool)));
+
+ // when
+ var valueHolder = Values.value(properties).as(ValueHolderWithOptionalNumber.class);
+
+ // then
+ assertEquals(string, valueHolder.string());
+ assertEquals(bytes, valueHolder.bytes());
+ assertEquals(bool, valueHolder.bool());
+ assertEquals(Long.MIN_VALUE, valueHolder.number());
+ }
+
+ public record ValueHolderWithOptionalNumber(String string, byte[] bytes, boolean bool, long number) {
+ public ValueHolderWithOptionalNumber(
+ @Property("string") String string, @Property("bytes") byte[] bytes, @Property("bool") boolean bool) {
+ this(string, bytes, bool, Long.MIN_VALUE);
+ }
+ }
+}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/BooleanValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/BooleanValueTest.java
index a5af38c8fa..768181f167 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/value/BooleanValueTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/BooleanValueTest.java
@@ -19,12 +19,18 @@
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.neo4j.driver.internal.value.BooleanValue.FALSE;
import static org.neo4j.driver.internal.value.BooleanValue.TRUE;
+import java.io.Serializable;
+import java.lang.constant.Constable;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.neo4j.driver.Values;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.internal.types.TypeConstructor;
import org.neo4j.driver.types.TypeSystem;
@@ -88,4 +94,15 @@ void shouldConvertToBooleanAndObject() {
assertThat(TRUE.asObject(), equalTo((Object) Boolean.TRUE));
assertThat(FALSE.asObject(), equalTo((Object) Boolean.FALSE));
}
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ void shouldMapToType(boolean expected) {
+ var value = Values.value(expected);
+ assertEquals(expected, value.as(boolean.class));
+ assertEquals(expected, value.as(Boolean.class));
+ assertEquals(expected, value.as(Serializable.class));
+ assertEquals(expected, value.as(Constable.class));
+ assertEquals(expected, value.as(Object.class));
+ }
}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/BytesValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/BytesValueTest.java
index 7b389ea44c..ccbe7cee7f 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/value/BytesValueTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/BytesValueTest.java
@@ -19,10 +19,13 @@
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.Test;
import org.neo4j.driver.Value;
+import org.neo4j.driver.Values;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.internal.types.TypeConstructor;
import org.neo4j.driver.types.TypeSystem;
@@ -86,4 +89,12 @@ void shouldHaveBytesType() {
InternalValue value = new BytesValue(TEST_BYTES);
assertThat(value.type(), equalTo(InternalTypeSystem.TYPE_SYSTEM.BYTES()));
}
+
+ @Test
+ void shouldMapToType() {
+ var bytes = new byte[] {0};
+ var values = Values.value(bytes);
+ assertEquals(bytes, values.as(byte[].class));
+ assertNotNull(values.as(Object.class));
+ }
}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/DateTimeValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/DateTimeValueTest.java
index 36832261e1..af6bf83837 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/value/DateTimeValueTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/DateTimeValueTest.java
@@ -19,10 +19,16 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.io.Serializable;
+import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
+import java.time.chrono.ChronoZonedDateTime;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAccessor;
import org.junit.jupiter.api.Test;
+import org.neo4j.driver.Values;
import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.types.InternalTypeSystem;
@@ -66,4 +72,18 @@ void shouldNotSupportAsLong() {
assertThrows(Uncoercible.class, dateTimeValue::asLong);
}
+
+ @Test
+ void shouldMapToType() {
+ var date = ZonedDateTime.now();
+ var values = Values.value(date);
+ assertEquals(date, values.as(ZonedDateTime.class));
+ assertEquals(date, values.as(Temporal.class));
+ assertEquals(date, values.as(TemporalAccessor.class));
+ assertEquals(date, values.as(ChronoZonedDateTime.class));
+ assertEquals(date, values.as(Comparable.class));
+ assertEquals(date, values.as(Serializable.class));
+ assertEquals(date, values.as(Object.class));
+ assertEquals(date.toOffsetDateTime(), values.as(OffsetDateTime.class));
+ }
}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/DateValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/DateValueTest.java
index e8e79eba95..09f78b3852 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/value/DateValueTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/DateValueTest.java
@@ -19,8 +19,13 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.io.Serializable;
import java.time.LocalDate;
+import java.time.chrono.ChronoLocalDate;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAdjuster;
import org.junit.jupiter.api.Test;
+import org.neo4j.driver.Values;
import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.types.InternalTypeSystem;
@@ -53,4 +58,17 @@ void shouldNotSupportAsLong() {
assertThrows(Uncoercible.class, dateValue::asLong);
}
+
+ @Test
+ void shouldMapToType() {
+ var date = LocalDate.now();
+ var values = Values.value(date);
+ assertEquals(date, values.as(LocalDate.class));
+ assertEquals(date, values.as(Temporal.class));
+ assertEquals(date, values.as(TemporalAdjuster.class));
+ assertEquals(date, values.as(ChronoLocalDate.class));
+ assertEquals(date, values.as(Comparable.class));
+ assertEquals(date, values.as(Serializable.class));
+ assertEquals(date, values.as(Object.class));
+ }
}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/DurationValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/DurationValueTest.java
index e8412ef8ef..2790c0863e 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/value/DurationValueTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/DurationValueTest.java
@@ -18,8 +18,11 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+import java.time.temporal.TemporalAmount;
import org.junit.jupiter.api.Test;
+import org.neo4j.driver.Values;
import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.InternalIsoDuration;
import org.neo4j.driver.internal.types.InternalTypeSystem;
@@ -55,6 +58,15 @@ void shouldNotSupportAsLong() {
assertThrows(Uncoercible.class, durationValue::asLong);
}
+ @Test
+ void shouldMapToType() {
+ var date = mock(IsoDuration.class);
+ var values = Values.value(date);
+ assertEquals(date, values.as(IsoDuration.class));
+ assertEquals(date, values.as(TemporalAmount.class));
+ assertEquals(date, values.as(Object.class));
+ }
+
private static IsoDuration newDuration(long months, long days, long seconds, int nanoseconds) {
return new InternalIsoDuration(months, days, seconds, nanoseconds);
}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/FloatValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/FloatValueTest.java
index 51e5fbc2ca..1a55e1493e 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/value/FloatValueTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/FloatValueTest.java
@@ -19,11 +19,16 @@
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.io.Serializable;
+import java.lang.constant.Constable;
+import java.lang.constant.ConstantDesc;
import org.junit.jupiter.api.Test;
import org.neo4j.driver.Value;
+import org.neo4j.driver.Values;
import org.neo4j.driver.exceptions.value.LossyCoercion;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.internal.types.TypeConstructor;
@@ -117,4 +122,25 @@ void shouldThrowIfSmallerThanIntegerMin() {
assertThat(value1.asInt(), equalTo(Integer.MIN_VALUE));
assertThrows(LossyCoercion.class, value2::asInt);
}
+
+ @SuppressWarnings("ConstantValue")
+ @Test
+ void shouldMapToType() {
+ var expected = 0.0;
+ var value = Values.value(expected);
+ assertEquals(expected, value.as(double.class));
+ assertEquals(expected, value.as(Double.class));
+ assertEquals(expected, value.as(Number.class));
+ assertEquals(expected, value.as(Serializable.class));
+ assertEquals(expected, value.as(Comparable.class));
+ assertEquals(expected, value.as(Constable.class));
+ assertEquals(expected, value.as(ConstantDesc.class));
+ assertEquals(expected, value.as(Object.class));
+ assertEquals((long) expected, value.as(long.class));
+ assertEquals((long) expected, value.as(Long.class));
+ assertEquals((int) expected, value.as(int.class));
+ assertEquals((int) expected, value.as(Integer.class));
+ assertEquals((float) expected, value.as(float.class));
+ assertEquals((float) expected, value.as(Float.class));
+ }
}
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 9ffb75f381..6403b602f3 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
@@ -19,11 +19,16 @@
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.io.Serializable;
+import java.lang.constant.Constable;
+import java.lang.constant.ConstantDesc;
import org.junit.jupiter.api.Test;
import org.neo4j.driver.Value;
+import org.neo4j.driver.Values;
import org.neo4j.driver.exceptions.value.LossyCoercion;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.internal.types.TypeConstructor;
@@ -124,4 +129,24 @@ void shouldThrowIfLargerThan() {
assertThat(value1.asDouble(), equalTo(9007199254740992D));
assertThrows(LossyCoercion.class, value2::asDouble);
}
+
+ @Test
+ void shouldMapToType() {
+ var expected = 0L;
+ var value = Values.value(expected);
+ assertEquals(expected, value.as(long.class));
+ assertEquals(expected, value.as(Long.class));
+ assertEquals(expected, value.as(Number.class));
+ assertEquals(expected, value.as(Serializable.class));
+ assertEquals(expected, value.as(Comparable.class));
+ assertEquals(expected, value.as(Constable.class));
+ assertEquals(expected, value.as(ConstantDesc.class));
+ assertEquals((int) expected, value.as(int.class));
+ assertEquals((int) expected, value.as(Integer.class));
+ assertEquals(expected, value.as(Object.class));
+ assertEquals(expected, value.as(double.class));
+ assertEquals(expected, value.as(Double.class));
+ assertEquals((float) expected, value.as(float.class));
+ assertEquals((float) expected, value.as(Float.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 01c25169fb..97e5be2e80 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,10 +18,14 @@
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.neo4j.driver.Values.value;
+import java.util.Collection;
+import java.util.List;
import org.junit.jupiter.api.Test;
import org.neo4j.driver.Value;
+import org.neo4j.driver.Values;
import org.neo4j.driver.internal.types.InternalTypeSystem;
class ListValueTest {
@@ -39,6 +43,16 @@ void shouldHaveCorrectType() {
assertThat(listValue.type(), equalTo(InternalTypeSystem.TYPE_SYSTEM.LIST()));
}
+ @Test
+ void shouldMapToType() {
+ var list = List.of(0L);
+ var values = Values.value(list);
+ assertEquals(list, values.as(List.class));
+ assertEquals(list, values.as(Collection.class));
+ assertEquals(list, values.as(Iterable.class));
+ assertEquals(list, values.as(Object.class));
+ }
+
private ListValue listValue(Value... values) {
return new ListValue(values);
}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/LocalDateTimeValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/LocalDateTimeValueTest.java
index b3ec2af499..167b846e46 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/value/LocalDateTimeValueTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/LocalDateTimeValueTest.java
@@ -22,8 +22,13 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.io.Serializable;
import java.time.LocalDateTime;
+import java.time.chrono.ChronoLocalDateTime;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAdjuster;
import org.junit.jupiter.api.Test;
+import org.neo4j.driver.Values;
import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.types.InternalTypeSystem;
@@ -56,4 +61,17 @@ void shouldNotSupportAsLong() {
assertThrows(Uncoercible.class, dateTimeValue::asLong);
}
+
+ @Test
+ void shouldMapToType() {
+ var date = LocalDateTime.now();
+ var values = Values.value(date);
+ assertEquals(date, values.as(LocalDateTime.class));
+ assertEquals(date, values.as(Temporal.class));
+ assertEquals(date, values.as(TemporalAdjuster.class));
+ assertEquals(date, values.as(ChronoLocalDateTime.class));
+ assertEquals(date, values.as(Comparable.class));
+ assertEquals(date, values.as(Serializable.class));
+ assertEquals(date, values.as(Object.class));
+ }
}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/LocalTimeValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/LocalTimeValueTest.java
index cb2b8b3265..44d4fc0eca 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/value/LocalTimeValueTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/LocalTimeValueTest.java
@@ -19,8 +19,12 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.io.Serializable;
import java.time.LocalTime;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAdjuster;
import org.junit.jupiter.api.Test;
+import org.neo4j.driver.Values;
import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.types.InternalTypeSystem;
@@ -53,4 +57,16 @@ void shouldNotSupportAsLong() {
assertThrows(Uncoercible.class, timeValue::asLong);
}
+
+ @Test
+ void shouldMapToType() {
+ var date = LocalTime.now();
+ var values = Values.value(date);
+ assertEquals(date, values.as(LocalTime.class));
+ assertEquals(date, values.as(Temporal.class));
+ assertEquals(date, values.as(TemporalAdjuster.class));
+ assertEquals(date, values.as(Comparable.class));
+ assertEquals(date, values.as(Serializable.class));
+ assertEquals(date, values.as(Object.class));
+ }
}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/MapValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/MapValueTest.java
index fb902c44df..a9bbe33cdd 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/value/MapValueTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/MapValueTest.java
@@ -18,12 +18,15 @@
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.neo4j.driver.Values.value;
import java.util.HashMap;
+import java.util.Map;
import org.junit.jupiter.api.Test;
import org.neo4j.driver.Value;
+import org.neo4j.driver.Values;
import org.neo4j.driver.internal.types.InternalTypeSystem;
class MapValueTest {
@@ -54,6 +57,14 @@ void shouldNotBeNull() {
assertFalse(map.isNull());
}
+ @Test
+ void shouldMapToType() {
+ var map = Map.of("key", "value");
+ var values = Values.value(map);
+ assertEquals(map, values.as(Map.class));
+ assertEquals(map, values.as(Object.class));
+ }
+
private MapValue mapValue() {
var map = new HashMap();
map.put("k1", value("v1"));
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/NodeValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/NodeValueTest.java
index cbee4ade8b..5cced72349 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/value/NodeValueTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/NodeValueTest.java
@@ -24,8 +24,13 @@
import static org.neo4j.driver.internal.util.ValueFactory.filledNodeValue;
import org.junit.jupiter.api.Test;
+import org.neo4j.driver.Values;
+import org.neo4j.driver.internal.InternalNode;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.internal.types.TypeConstructor;
+import org.neo4j.driver.types.Entity;
+import org.neo4j.driver.types.MapAccessor;
+import org.neo4j.driver.types.Node;
class NodeValueTest {
@Test
@@ -55,4 +60,14 @@ void shouldTypeAsNode() {
InternalValue value = emptyNodeValue();
assertThat(value.typeConstructor(), equalTo(TypeConstructor.NODE));
}
+
+ @Test
+ void shouldMapToType() {
+ var node = new InternalNode(0);
+ var values = Values.value(node);
+ assertEquals(node, values.as(Node.class));
+ assertEquals(node, values.as(Entity.class));
+ assertEquals(node, values.as(MapAccessor.class));
+ assertEquals(node, values.as(Object.class));
+ }
}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/NullValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/NullValueTest.java
index 75ee6fafd0..cb00e833e3 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/value/NullValueTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/NullValueTest.java
@@ -20,6 +20,7 @@
import static java.util.Collections.emptyMap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.neo4j.driver.Values.isoDuration;
import static org.neo4j.driver.Values.ofValue;
@@ -34,6 +35,7 @@
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import org.neo4j.driver.Value;
+import org.neo4j.driver.Values;
import org.neo4j.driver.internal.types.TypeConstructor;
class NullValueTest {
@@ -120,6 +122,14 @@ void shouldReturnAsDefaultValue() {
assertComputeOrDefaultReturnDefault(Value::asNumber, 10);
}
+ @Test
+ void shouldMapToType() {
+ var values = Values.value((Object) null);
+ // unlike asString(), this returns null
+ assertNull(values.as(String.class));
+ assertNull(values.as(Object.class));
+ }
+
private static void assertComputeOrDefaultReturnDefault(Function f, T defaultAndExpectedValue) {
var value = NullValue.NULL;
assertThat(value.computeOrDefault(f, defaultAndExpectedValue), equalTo(defaultAndExpectedValue));
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/PathValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/PathValueTest.java
index 8d445869e7..eb2d4058b3 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/value/PathValueTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/PathValueTest.java
@@ -24,7 +24,12 @@
import org.junit.jupiter.api.Test;
import org.neo4j.driver.Value;
+import org.neo4j.driver.Values;
+import org.neo4j.driver.internal.InternalNode;
+import org.neo4j.driver.internal.InternalPath;
+import org.neo4j.driver.internal.InternalRelationship;
import org.neo4j.driver.internal.types.InternalTypeSystem;
+import org.neo4j.driver.types.Path;
class PathValueTest {
@Test
@@ -42,4 +47,14 @@ void shouldNotBeNull() {
void shouldHaveCorrectType() {
assertThat(filledPathValue().type(), equalTo(InternalTypeSystem.TYPE_SYSTEM.PATH()));
}
+
+ @Test
+ void shouldMapToType() {
+ var path = new InternalPath(
+ new InternalNode(42L), new InternalRelationship(43L, 42L, 44L, "T"), new InternalNode(44L));
+ var values = Values.value(path);
+ assertEquals(path, values.as(Path.class));
+ assertEquals(path, values.as(Iterable.class));
+ assertEquals(path, values.as(Object.class));
+ }
}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/PointValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/PointValueTest.java
new file mode 100644
index 0000000000..0f17503204
--- /dev/null
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/PointValueTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.neo4j.driver.internal.value;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import org.junit.jupiter.api.Test;
+import org.neo4j.driver.Values;
+import org.neo4j.driver.types.Point;
+
+class PointValueTest {
+
+ @Test
+ void shouldMapToType() {
+ var point = mock(Point.class);
+ var values = Values.value(point);
+ assertEquals(point, values.as(Point.class));
+ assertEquals(point, values.as(Object.class));
+ }
+}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/RelationshipValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/RelationshipValueTest.java
index 1d871c2e84..c0bcb5f2a6 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/value/RelationshipValueTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/RelationshipValueTest.java
@@ -25,7 +25,12 @@
import org.junit.jupiter.api.Test;
import org.neo4j.driver.Value;
+import org.neo4j.driver.Values;
+import org.neo4j.driver.internal.InternalRelationship;
import org.neo4j.driver.internal.types.TypeConstructor;
+import org.neo4j.driver.types.Entity;
+import org.neo4j.driver.types.MapAccessor;
+import org.neo4j.driver.types.Relationship;
class RelationshipValueTest {
@Test
@@ -51,4 +56,14 @@ void shouldTypeAsRelationship() {
InternalValue value = emptyRelationshipValue();
assertThat(value.typeConstructor(), equalTo(TypeConstructor.RELATIONSHIP));
}
+
+ @Test
+ void shouldMapToType() {
+ var relationship = new InternalRelationship(0, 0, 0, "value");
+ var values = Values.value(relationship);
+ assertEquals(relationship, values.as(Relationship.class));
+ assertEquals(relationship, values.as(Entity.class));
+ assertEquals(relationship, values.as(MapAccessor.class));
+ assertEquals(relationship, values.as(Object.class));
+ }
}
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 d353ed4c25..7dd5df8ea3 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
@@ -19,10 +19,15 @@
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import java.io.Serializable;
+import java.lang.constant.Constable;
+import java.lang.constant.ConstantDesc;
import org.junit.jupiter.api.Test;
import org.neo4j.driver.Value;
+import org.neo4j.driver.Values;
import org.neo4j.driver.internal.types.InternalTypeSystem;
import org.neo4j.driver.internal.types.TypeConstructor;
import org.neo4j.driver.types.TypeSystem;
@@ -84,4 +89,17 @@ void shouldHaveStringType() {
InternalValue value = new StringValue("Spongebob");
assertThat(value.type(), equalTo(InternalTypeSystem.TYPE_SYSTEM.STRING()));
}
+
+ @Test
+ void shouldMapToType() {
+ var string = "value";
+ var values = Values.value(string);
+ assertEquals(string, values.as(String.class));
+ assertEquals(string, values.as(Serializable.class));
+ assertEquals(string, values.as(Comparable.class));
+ assertEquals(string, values.as(CharSequence.class));
+ assertEquals(string, values.as(Constable.class));
+ assertEquals(string, values.as(ConstantDesc.class));
+ assertEquals(string, values.as(Object.class));
+ }
}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/value/TimeValueTest.java b/driver/src/test/java/org/neo4j/driver/internal/value/TimeValueTest.java
index 155e547096..7860b44063 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/value/TimeValueTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/value/TimeValueTest.java
@@ -19,9 +19,13 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.io.Serializable;
import java.time.OffsetTime;
import java.time.ZoneOffset;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAdjuster;
import org.junit.jupiter.api.Test;
+import org.neo4j.driver.Values;
import org.neo4j.driver.exceptions.value.Uncoercible;
import org.neo4j.driver.internal.types.InternalTypeSystem;
@@ -54,4 +58,16 @@ void shouldNotSupportAsLong() {
assertThrows(Uncoercible.class, timeValue::asLong);
}
+
+ @Test
+ void shouldMapToType() {
+ var date = OffsetTime.now();
+ var values = Values.value(date);
+ assertEquals(date, values.as(OffsetTime.class));
+ assertEquals(date, values.as(Temporal.class));
+ assertEquals(date, values.as(TemporalAdjuster.class));
+ assertEquals(date, values.as(Comparable.class));
+ assertEquals(date, values.as(Serializable.class));
+ assertEquals(date, values.as(Object.class));
+ }
}