-
Notifications
You must be signed in to change notification settings - Fork 155
feat: Introduce Value and Record mapping to custom object types #1633
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
b75a146
to
bc857bd
Compare
6510b5c
to
cf2ee02
Compare
043ee17
to
d7cd56e
Compare
0060dcc
to
5fc5a37
Compare
Please note that this is a [feature preview](https://github.com/neo4j/neo4j-java-driver/blob/5.0/README.md#preview-features). This update brings support for mapping `org.neo4j.driver.Value` and `org.neo4j.driver.Record` to custom object types. ## org.neo4j.driver.Value The following new method has been introduced to `org.neo4j.driver.Value`: ```java <T> T as(Class<T> targetClass) ``` It maps the value to the given type providing that is it supported. ### Basic Mapping Supported destination types depend on the value `org.neo4j.driver.types.Type`, please see the supported mappings below. - `TypeSystem#BOOLEAN` -> `boolean`, `Boolean` - `TypeSystem#BYTES` -> `byte[]` - `TypeSystem#STRING` -> `String` - `TypeSystem#INTEGER` -> `long`, `Long`, `int`, `Integer`, `double`, `Double`, `float`, `Float` - `TypeSystem#FLOAT` -> `long`, `Long`, `int`, `Integer`, `double`, `Double`, `float`, `Float` - `TypeSystem#PATH` -> `Path` - `TypeSystem#POINT` -> `Point` - `TypeSystem#DATE` -> `LocalDate` - `TypeSystem#TIME` -> `OffsetTime` - `TypeSystem#LOCAL_TIME` -> `LocalTime` - `TypeSystem#LOCAL_DATE_TIME` -> `LocalDateTime` - `TypeSystem#DATE_TIME` -> `ZonedDateTime`, `OffsetDateTime` - `TypeSystem#DURATION` -> `IsoDuration`, `java.time.Period` (only when `seconds = 0` and `nanoseconds = 0` and no overflow happens), `java.time.Duration` (only when `months = 0` and `days = 0` and no overflow happens) - `TypeSystem#NULL` -> `null` - `TypeSystem#LIST` -> `List` - `TypeSystem#MAP` -> `Map` - `TypeSystem#NODE` -> `Node` - `TypeSystem#RELATIONSHIP` -> `Relationship` ### Object Mapping Mapping of user-defined properties to user-defined types is supported for the following value types: - `TypeSystem#NODE` - `TypeSystem#RELATIONSHIP` - `TypeSystem#MAP` Example (using the [Neo4j Movies Database](https://github.com/neo4j-graph-examples/movies)): ```java // 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 `org.neo4j.driver.types.MapAccessor`. If Object Graph Mapping (OGM) is needed, please use a higher level solution built on top of the driver, like [Spring Data Neo4j](https://neo4j.com/docs/getting-started/languages-guides/java/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 `org.neo4j.driver.mapping.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 `-parameters` option is used or they belong to the cannonical constructor of `java.lang.Record`). The name matching is case-sensitive. Additionally, the `org.neo4j.driver.mapping.Property` annotation may be used when mapping a property with a different name to `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 `java.lang.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 `null` value is used for arguments that don't have a matching property. If the argument does not accept `null` value (this includes primitive types), an alternative constructor that excludes it must be available. Example with optional property (using the [Neo4j Movies Database](https://github.com/neo4j-graph-examples/movies)): ```java // 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](https://github.com/neo4j-graph-examples/movies)): ```java // assuming the following Java record public record Acted(List<String> 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: ```java public record Acted<T>(List<T> roles) {} ``` Wildcard type value is not supported. ## org.neo4j.driver.Record The following new method has been introduced to `org.neo4j.driver.Record`: ```java <T> T as(Class<T> targetClass) ``` It maps values of this record to properties of the given type providing that is it supported. Example (using the [Neo4j Movies Database](https://github.com/neo4j-graph-examples/movies)): ```java // assuming the following Java record public record MovieInfo(String title, String director, List<String> actors) {} // the record values may be mapped to MovieInfo instances var movies = driver.executableQuery("MATCH (actor:Person)-[:ACTED_IN]->(movie:Movie)<-[:DIRECTED]-(director:Person) RETURN movie.title as title, director.name AS director, collect(actor.name) Ators") .execute() .records() .stream() .map(record -> record.as(MovieInfo.class)) .toList(); } ``` It follows the same set of rules and has the same requirements as described in the `Object Mapping` `Value` section above.
5fc5a37
to
32d49b0
Compare
michael-simons
approved these changes
Apr 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wonderful commit message, very well explained.
I quickly had a look at ObjectMapper and friends, nothing to complain. If you want me to look at something in detail, please point me to it.
I like that it's actually not much change.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Please note that this is a feature preview.
This update brings support for mapping
org.neo4j.driver.Value
andorg.neo4j.driver.Record
to custom object types.org.neo4j.driver.Value
The following new method has been introduced to
org.neo4j.driver.Value
:It maps the value to the given type providing that is it supported.
Basic Mapping
Supported destination types depend on the value
org.neo4j.driver.types.Type
, please see the supported mappings below.TypeSystem#BOOLEAN
->boolean
,Boolean
TypeSystem#BYTES
->byte[]
TypeSystem#STRING
->String
TypeSystem#INTEGER
->long
,Long
,int
,Integer
,double
,Double
,float
,Float
TypeSystem#FLOAT
->long
,Long
,int
,Integer
,double
,Double
,float
,Float
TypeSystem#PATH
->Path
TypeSystem#POINT
->Point
TypeSystem#DATE
->LocalDate
TypeSystem#TIME
->OffsetTime
TypeSystem#LOCAL_TIME
->LocalTime
TypeSystem#LOCAL_DATE_TIME
->LocalDateTime
TypeSystem#DATE_TIME
->ZonedDateTime
,OffsetDateTime
TypeSystem#DURATION
->IsoDuration
,java.time.Period
(only whenseconds = 0
andnanoseconds = 0
and no overflow happens),java.time.Duration
(only whenmonths = 0
anddays = 0
and no overflow happens)TypeSystem#NULL
->null
TypeSystem#LIST
->List
TypeSystem#MAP
->Map
TypeSystem#NODE
->Node
TypeSystem#RELATIONSHIP
->Relationship
Object Mapping
Mapping of user-defined properties to user-defined types is supported for the following value types:
TypeSystem#NODE
TypeSystem#RELATIONSHIP
TypeSystem#MAP
Example (using the Neo4j Movies Database):
Note that Object Mapping is an alternative to accessing the user-defined values in a
org.neo4j.driver.types.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
org.neo4j.driver.mapping.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-parameters
option is used or they belong to the cannonical constructor ofjava.lang.Record
). The name matching is case-sensitive.Additionally, the
org.neo4j.driver.mapping.Property
annotation may be used when mapping a property with a different name tojava.lang.Record
cannonical constructor parameter.The constructor selection criteria is the following (top priority first):
The constructor search is done in the order defined by the
java.lang.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
null
value is used for arguments that don't have a matching property. If the argument does not acceptnull
value (this includes primitive types), an alternative constructor that excludes it must be available.Example with optional property (using the Neo4j Movies Database):
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):
On the contrary, the following record would not be mapped because the type information is insufficient:
Wildcard type value is not supported.
org.neo4j.driver.Record
The following new method has been introduced to
org.neo4j.driver.Record
:It maps values of this record to properties of the given type providing that is it supported.
Example (using the Neo4j Movies Database):
It follows the same set of rules and has the same requirements as described in the
Object Mapping
Value
section above.