Transformer
Gremlinq converts between .NET types and the graph database's wire format through a pair of transformer chains — one for serialization (.NET → database) and one for deserialization (database → .NET). Every custom type that is not natively understood by the database must be registered in these chains; this page describes how the mechanism works.
Concrete examples of registering custom types are in Custom types.
ITransformer — the chain
An ITransformer is an ordered collection of IConverterFactory instances. The two chains live
on IGremlinQueryEnvironment as Serializer and Deserializer. You never create them directly;
you extend them through ConfigureSerializer and ConfigureDeserializer.
public interface ITransformer
{
bool TryTransform<TSource, TTarget>(
TSource source,
IGremlinQueryEnvironment environment,
[NotNullWhen(true)] out TTarget? value);
ITransformer Add(IConverterFactory converterFactory);
}
Add returns a new ITransformer with the factory appended; the original is unchanged.
Factories are tried in reverse registration order — the factory added last is tried first,
so adding a factory always gives it higher priority than everything already registered.
IConverterFactory — negotiating the type pair
A factory is invoked once per unique (TSource, TTarget) type pair. Its job is to decide
whether it can produce a converter for that pair. If it can, it returns an
IConverter<TSource, TTarget>; if not, it returns null and the chain tries the previous
factory.
public interface IConverterFactory
{
IConverter<TSource, TTarget>? TryCreate<TSource, TTarget>(IGremlinQueryEnvironment environment);
}
The environment parameter provides access to native types, options, and the rest of the query
environment at the moment the converter is constructed. Inspect typeof(TSource) and
typeof(TTarget) inside TryCreate to decide whether to handle the pair.
A factory that handles every enum type, for example, would return a converter when
typeof(TSource).IsEnum is true and typeof(TTarget).IsAssignableFrom(typeof(string)) is
true, and null for all other type pairs.
IConverter — performing the conversion
A converter handles one value of the negotiated type pair. The bool return signals success;
out value carries the result and is meaningful only when true is returned.
public interface IConverter<in TSource, TTarget>
{
bool TryConvert(
TSource source,
ITransformer defer,
ITransformer recurse,
[NotNullWhen(true)] out TTarget? value);
}
The two transformer parameters allow calling back into the pipeline:
defer— contains only the factories registered before this one. Use it when you need to convert a sub-value without risk of re-entering your own converter and looping indefinitely.recurse— contains the entire chain. Use it when you need to delegate conversion of a nested structure (e.g. the elements of a list) through the full pipeline.
Most simple scalar converters ignore both parameters and compute the result directly.
Configuration entry points
IGremlinQueryEnvironment exposes three methods for extending the pipeline:
IGremlinQueryEnvironment ConfigureSerializer(
Func<ITransformer, ITransformer> transformation);
IGremlinQueryEnvironment ConfigureDeserializer(
Func<ITransformer, ITransformer> transformation);
IGremlinQueryEnvironment ConfigureNativeTypes(
Func<IImmutableSet<Type>, IImmutableSet<Type>> transformation);
Each method is immutable — it returns a new IGremlinQueryEnvironment rather than modifying
the existing one. The function you pass receives the current transformer and returns the modified
one; the typical idiom is to call .Add(yourFactory) on it.
ConfigureNativeTypes maintains the set of types the environment treats as first-class scalar
values. Without registering a type here, Gremlinq may try to decompose the value structurally
— as though it were a vertex or edge — rather than routing it through the scalar converter chain.
Always call ConfigureNativeTypes alongside ConfigureSerializer and ConfigureDeserializer
when registering a new scalar type.
Resolution order and fallback
When TryTransform<TSource, TTarget> is called, the transformer walks the factory list from
last to first. The first factory that returns a non-null converter for the type pair wins;
that converter's TryConvert is then invoked. If TryConvert returns false, the chain
continues to the next matching factory. If no factory produces a successful conversion, the
transformer tries a direct cast (source is TTarget). If that also fails, it returns false.
Next: Custom types — registering enums, Vogen value objects, and other domain types using this infrastructure.