Supported types
Type | Query result | Query parameter |
---|---|---|
scala.Boolean |
✓ | ✓ |
scala.Int |
✓ | ✓ |
scala.Long |
✓ | ✓ |
scala.Double |
✓ | ✓ |
scala.Float |
✓ | ✓ |
java.lang.String |
✓ | ✓ |
scala.Array[Byte] |
✓ | ✓ |
scala.Option[T] * ** |
✓ | ✓ |
scala.collection._ * *** |
✓ | ✓ |
refined.Refined[T, P] * + |
✓ | ✓ |
cats.data.Chain[T] * ++ |
✓ | ✓ |
cats.data.Const[T, U] * ++ |
✓ | ✓ |
cats.data.NonEmptyChain[T] * ++ |
✓ | ✓ |
cats.data.NonEmptyList[T] * ++ |
✓ | ✓ |
cats.data.NonEmptyMap[K, V] * ++ |
✓ | ✓ |
cats.data.NonEmptySet[T] * ++ |
✓ | ✓ |
cats.data.NonEmptyVector[T] * ++ |
✓ | ✓ |
enumeratum.Enum +++ |
✓ | ✓ |
enumeratum.values.ValueEnum +++ |
✓ | ✓ |
java.time.Duration |
✓ | ✓ |
java.time.LocalDate |
✓ | ✓ |
java.time.LocalDateTime |
✓ | ✓ |
java.time.LocalTime |
✓ | ✓ |
java.time.Period |
✓ | ✓ |
java.time.OffsetDateTime |
✓ | ✓ |
java.time.OffsetTime |
✓ | ✓ |
java.time.ZonedDateTime |
✓ | ✓ |
java.util.UUID |
✓ | ✓ |
neotypes.DeferredQuery °° |
✓ | |
org.neo4j.driver.Value |
✓ | ✓ |
org.neo4j.driver.types.IsoDuration |
✓ | ✓ |
org.neo4j.driver.types.Point |
✓ | ✓ |
org.neo4j.driver.types.Node |
✓ | |
org.neo4j.driver.types.Relationship |
✓ | |
neotypes.types.Path |
✓ | |
shapeless.HList ° |
✓ | ✓ |
Tuple (1-22) ° |
✓ | ✓ |
User defined case class ° |
✓ | ✓ |
*
Generic types are supported as long as the theT
is also supported.
**
None
is converted intonull
.
***
Map-like collections are supported only if their keys and values are also supported.
+
Support is provided in theneotypes-refined
module.
++
Support is provided in theneotypes-cats-data
module.
+++
Support is provided in theneotypes-enumeratum
module.
°
Support for automatic derivation is provided in thegeneric
module.
°°
Nested queries are expanded at compile time.
Additional types
If you want to support your own types, then you would need to create your own implicits.
- For query results, you need an instance of
neotypes.mappers.ResultMapper[T]
. You can create a new instance:- Combine existing instances using the provided combinators.
- Or from scratch by instantiating it
ResultMapper.instance[T] { value => ... }
(you rarely would need to resort to this).
- For query parameters, you need an instance of
neotypes.mappers.ParameterMapper[T]
. You can create a new instance:- Transforming an already existing mapper using
contramap
.
- Transforming an already existing mapper using
- For keys in maps, you need an instance of
neotypes.mappers.KeyMapper[T]
You can create a new instance:- Transforming an already existing mapper using
imap
.
- Transforming an already existing mapper using
Combinators
ResultMapper
is not designed to be used a typeclass but rather as an explicit value.
You can appreciate that since the query
method on DeferredQueryBuilder
expects a explicit ResultMapper
, rather than an implicit one.
The idea is that instead of using implicit resolution to derive instances for your types, you will use explicit combinators provided by the library to construct such instances.
import neotypes.generic.implicits._ // Provides automatic derivation of ResultMapper for any case class. // Provides automatic derivation of ResultMapper for any case class.
import neotypes.mappers.ResultMapper
import neotypes.model.exceptions.IncoercibleException
import ResultMapper._ // Put all combinators in scope. // Put all combinators in scope.
// Mappers for primitive types.
int
// res0: ResultMapper[Int] = neotypes.mappers.ResultMapper$$anon$1@7b7416d2
string
// res1: ResultMapper[String] = neotypes.mappers.ResultMapper$$anon$1@2b3a5bda
// Mappers for the stdlib data types.
option(int)
// res2: ResultMapper[Option[Int]] = neotypes.mappers.ResultMapper$$anon$1@749c81fe
either(boolean, bytes)
// res3: ResultMapper[Either[Boolean, collection.immutable.ArraySeq[Byte]]] = neotypes.mappers.ResultMapper$$anon$1@658377ce
list(string)
// res4: ResultMapper[List[String]] = neotypes.mappers.ResultMapper$$anon$1@3e9a3f2a
collectAs(collection.immutable.BitSet, int)
// res5: ResultMapper[collection.immutable.BitSet] = neotypes.mappers.ResultMapper$$anon$1@478747a8
// Mappers for tuples.
tuple(int, string)
// res6: ResultMapper[(Int, String)] = neotypes.mappers.ResultMapper$$anon$1@5784add7
tupleNamed("foo" -> int, "bar" -> string)
// res7: ResultMapper[(Int, String)] = neotypes.mappers.ResultMapper$$anon$1@29c78dee
// Mappers for case classes.
final case class Data(foo: Int, bar: String)
product(int, string)(Data.apply)
// res8: ResultMapper[Data] = neotypes.mappers.ResultMapper$$anon$1@46cb9493
productNamed("foo" -> int, "bar" -> string)(Data.apply)
// res9: ResultMapper[Data] = neotypes.mappers.ResultMapper$$anon$1@3b0c31e5
fromFunction(Data.apply _)
// res10: ResultMapper[Data] = neotypes.mappers.ResultMapper$$anon$1@4914d946
fromFunctionNamed("foo", "bar")(Data.apply _)
// res11: ResultMapper[Data] = neotypes.mappers.ResultMapper$$anon$1@5c298577
// Mapper for sealed traits.
sealed trait Problem extends Product with Serializable
object Problem {
final case class Error(msg: String) extends Problem
object Error {
implicit val resultMapper = ResultMapper.productDerive[Error]
}
final case class Warning(msg: String) extends Problem
object Warning {
implicit val resultMapper = ResultMapper.productDerive[Warning]
}
final case object Unknown extends Problem {
implicit val resultMapper = ResultMapper.constant(this)
}
}
val mapper = coproduct(strategy = CoproductDiscriminatorStrategy.RelationshipType)(
"error" -> Problem.Error.resultMapper,
"warning" -> Problem.Warning.resultMapper,
"unknown" -> Problem.Unknown.resultMapper
)
// mapper: ResultMapper[Problem] = neotypes.mappers.ResultMapper$$anon$1@46dcfd6d
// Apply custom validations.
final case class Id(int: Int)
object Id {
def from(int: Int): Option[Id] =
if (int >= 0) Some(Id(int)) else None
}
int.emap { i =>
Id.from(i).toRight(
left = IncoercibleException(s"${i} is not a valid ID because is negative")
)
}
// res12: ResultMapper[Id] = neotypes.mappers.ResultMapper$$anon$1@1c856f4a
You may check the
DriverSpec
for more examples. Also check the Scaladoc forResultMapper
The rationale for preferring combinators over typeclasses is best explained by Fabio Labella on the Dynosaur motivation page: https://systemfw.org/dynosaur/#/motivation
Having said that, if you still prefer the simplicity of automatic derivation,
you still can use ResultMapper
in a semi-implicit way.
query.query(ResultMapper[T]) // For any type.
query.query(ResultMapper.productDerive[Foo]) // Only for case classes / ADTs.
For more information about using combinators over typeclasses check Luis’s Master’s thesis: https://www.dropbox.com/s/tmx1p3y75uzg4ip/Memoria.pdf?dl=0 The main document is in Spanish but at the end is a small article in English.
Neo4j Id
Even if Neo4j does not recommend the use of the of the system id, neotypes allows you to easily retrieve it.
You only need to ask for a property named id
of type String
on your case classes.
Note: If your model also defines a custom id
property, then your property will take precedence and we will return you that one instead of the system one.
If you also need the system one then you can ask for the _id
property.
If you have a custom _id
property, then yours will take precedence and the system id will be available in the id
property.
If you define both id
and _id
as custom properties, then both will take precedence and the system id would be unreachable.
Disclaimer: we also discourage you from using the system id; we only allow you to access it because the Java driver does.