Class ShapeShiftSerializer<TEncoder, TDecoder>
- Namespace
- ShapeShift
- Assembly
- ShapeShift.dll
A format-agnostic base class for serializers that use specific encoders and decoders.
public abstract record ShapeShiftSerializer<TEncoder, TDecoder> : IEquatable<ShapeShiftSerializer<TEncoder, TDecoder>> where TEncoder : IEncoder, allows ref struct where TDecoder : IDecoder, allows ref struct
Type Parameters
TEncoderThe type of encoder to use.
TDecoderThe type of decoder to use.
- Inheritance
-
ShapeShiftSerializer<TEncoder, TDecoder>
- Implements
-
IEquatable<ShapeShiftSerializer<TEncoder, TDecoder>>
- Derived
- Inherited Members
Properties
ConverterFactories
Gets an array of converter factories to consult when creating a converter for a given type.
public ImmutableArray<IShapeShiftConverterFactory<TEncoder, TDecoder>> ConverterFactories { get; init; }
Property Value
- ImmutableArray<IShapeShiftConverterFactory<TEncoder, TDecoder>>
Remarks
Factories are the last resort for creating a custom converter, coming after ShapeShift.SerializerConfiguration<TEncoder, TDecoder>.Converters and ShapeShift.SerializerConfiguration<TEncoder, TDecoder>.ConverterTypes.
Converters
Gets an array of ShapeShiftConverter<T, TEncoder, TDecoder> objects that should be used for their designated data types.
public ConverterCollection<TEncoder, TDecoder> Converters { get; init; }
Property Value
- ConverterCollection<TEncoder, TDecoder>
Remarks
Converters in this collection are searched first when creating a converter for a given type, before ShapeShift.SerializerConfiguration<TEncoder, TDecoder>.ConverterTypes and ShapeShift.SerializerConfiguration<TEncoder, TDecoder>.ConverterFactories.
DeserializeDefaultValues
Gets the policy that controls missing and null values during deserialization.
public DeserializeDefaultValuesPolicy DeserializeDefaultValues { get; init; }
Property Value
InternStrings
Gets a value indicating whether to intern strings during deserialization.
public bool InternStrings { get; init; }
Property Value
Remarks
String interning means that a string that appears multiple times (within a single deserialization or across many) in the msgpack data will be deserialized as the same string instance, reducing GC pressure.
When enabled, all deserialized strings are retained with a weak reference, allowing them to be garbage collected while also being reusable for future deserializations as long as they are in memory.
This feature has a positive impact on memory usage but may have a negative impact on performance due to searching through previously deserialized strings to find a match. If your application is performance sensitive, you should measure the impact of this feature on your application.
This feature is orthogonal and complementary to ShapeShift.SerializerConfiguration<TEncoder, TDecoder>.PreserveReferences. Preserving references impacts the serialized result and can hurt interoperability if the other party is not using the same feature. Preserving references also does not guarantee that equal strings will be reused because the original serialization may have had multiple string objects for the same value, so deserialization would produce the same result. Preserving references alone will never reuse strings across top-level deserialization operations either. Interning strings however, has no impact on the serialized result and is always safe to use. Interning strings will guarantee string objects are reused within and across deserialization operations so long as their values are equal. The combination of the two features will ensure the most compact msgpack, and will produce faster deserialization times than string interning alone. Combining the two features also activates special behavior to ensure that serialization only writes a string once and references that string later in that same serialization, even if the equal strings were unique objects.
PreserveReferences
Gets a setting that determines how references to objects are preserved during serialization and deserialization.
public ReferencePreservationMode PreserveReferences { get; init; }
Property Value
- ReferencePreservationMode
The default value is Off because it requires no msgpack extensions, is compatible with all msgpack readers, adds no security considerations and is the most performant.
Remarks
Preserving references impacts the serialized result and can hurt interoperability if the other party is not using the same feature.
Exceptions
- NotSupportedException
Thrown when set to anything but Off on a serializer whose format does not implement IReferencePreservingSerializer<TEncoder, TDecoder>. Preserving references requires a format-specific way to write and recognize a back-reference, so a format that has not opted in cannot honor the request. Failing here reports that at configuration time rather than partway through the first serialization.
PropertyNamingPolicy
Gets the transformation function to apply to property names before serializing them.
public ShapeShiftNamingPolicy? PropertyNamingPolicy { get; init; }
Property Value
- ShapeShiftNamingPolicy
The default value is null, indicating that property names should be persisted exactly as they are declared in .NET.
SerializeDefaultValues
Gets the policy that controls whether properties with default values are serialized.
public SerializeDefaultValuesPolicy SerializeDefaultValues { get; init; }
Property Value
SerializeEnumValuesByName
Gets a value indicating whether enum values are serialized by name instead of by ordinal.
public bool SerializeEnumValuesByName { get; init; }
Property Value
StartingContext
Gets the starting context to begin (de)serializations with.
public SerializationContext<TEncoder, TDecoder> StartingContext { get; init; }
Property Value
- SerializationContext<TEncoder, TDecoder>
Methods
CreateDocumentReader<T>(ITypeShape<T>, CancellationToken)
Creates a reader that incrementally enumerates a sequence of whole top-level values sharing one decoder, such as newline-delimited JSON (NDJSON) or a buffer containing several concatenated values.
public ShapeShiftDocumentReader<T, TEncoder, TDecoder> CreateDocumentReader<T>(ITypeShape<T> typeShape, CancellationToken cancellationToken = default)
Parameters
typeShapeITypeShape<T>The shape of
T.cancellationTokenCancellationTokenA cancellation token that applies throughout the lifetime of the reader.
Returns
- ShapeShiftDocumentReader<T, TEncoder, TDecoder>
The reader. Callers should dispose of it (or use a using statement) when done.
Type Parameters
TThe type of each top-level value.
CreateSequenceReader<T>(ITypeShape<T>, CancellationToken)
Creates a reader that incrementally enumerates the elements of a vector, whether that vector is the root of a document or reached by first seeking into an enclosing document.
public ShapeShiftSequenceReader<T, TEncoder, TDecoder> CreateSequenceReader<T>(ITypeShape<T> typeShape, CancellationToken cancellationToken = default)
Parameters
typeShapeITypeShape<T>The shape of
T.cancellationTokenCancellationTokenA cancellation token that applies throughout the lifetime of the reader.
Returns
- ShapeShiftSequenceReader<T, TEncoder, TDecoder>
The reader. Callers should dispose of it (or use a using statement) when done.
Type Parameters
TThe type of each element in the vector.
CreateSerializationContext(ITypeShapeProvider, CancellationToken)
Creates a new serialization context that is ready to process a serialization job.
protected ShapeShiftSerializer<TEncoder, TDecoder>.DisposableSerializationContext CreateSerializationContext(ITypeShapeProvider provider, CancellationToken cancellationToken = default)
Parameters
providerITypeShapeProviderThe shape provider for the type(s) to be serialized. This might be PolyType.ReflectionProvider.ReflectionTypeShapeProvider.Default to use reflection-based shapes. It might also be the value of the
GeneratedTypeShapeProviderstatic property on a witness class (a class on which PolyType.GenerateShapeForAttribute<T> has been applied), although for source generated shapes, overloads that do not take an PolyType.ITypeShapeProvider offer better performance.cancellationTokenCancellationTokenA cancellation token for the operation.
Returns
- ShapeShiftSerializer<TEncoder, TDecoder>.DisposableSerializationContext
The serialization context.
Remarks
Callers should be sure to always call Dispose() when done with the context.
DeserializeFragment<T>(ref TDecoder, ShapeShiftPath, ITypeShape<T>, CancellationToken)
Deserializes the value found at a given ShapeShiftPath, skipping over everything else in the document without fully parsing or buffering it.
public T? DeserializeFragment<T>(ref TDecoder decoder, ShapeShiftPath path, ITypeShape<T> typeShape, CancellationToken cancellationToken = default)
Parameters
decoderTDecoderThe decoder, positioned wherever
pathshould be considered relative to (typically the start of the document).pathShapeShiftPathThe location of the value to deserialize.
typeShapeITypeShape<T>The shape of
T.cancellationTokenCancellationTokenA cancellation token.
Returns
- T
The deserialized value.
Type Parameters
TThe type to deserialize the fragment as.
Exceptions
- ShapeShiftSerializationException
Thrown when
pathcould not be found.- DecoderException
Thrown when a step along
pathexpects a map or vector but finds some other, non-null token.
Deserialize<T>(ref TDecoder, ITypeShape<T>, CancellationToken)
public T? Deserialize<T>(ref TDecoder decoder, ITypeShape<T> typeShape, CancellationToken cancellationToken = default)
Parameters
decoderTDecodertypeShapeITypeShape<T>cancellationTokenCancellationToken
Returns
- T
Type Parameters
T
GetContract(ITypeShape)
Describes the serialized form of a type in a format-neutral way.
public DataContract GetContract(ITypeShape typeShape)
Parameters
typeShapeITypeShapeThe shape of the type to describe.
Returns
- DataContract
The contract describing how values of this type are written and read.
Remarks
The returned contract reflects this serializer's configuration, including PropertyNamingPolicy, SerializeDefaultValues, DeserializeDefaultValues, SerializeEnumValuesByName and any registered Converters.
Custom converters that do not override GetContract(ContractContext<TEncoder, TDecoder>) are described with an UndocumentedContract instead of a guess.
Exceptions
- NotSupportedException
Thrown when PreserveReferences is enabled, because reference preservation replaces repeated values with references in a way that cannot be described statically.
GetContract<T>()
Describes the serialized form of a type in a format-neutral way.
public DataContract GetContract<T>() where T : IShapeable<T>
Returns
- DataContract
The contract describing how values of this type are written and read.
Type Parameters
TThe type to describe.
Remarks
The returned contract reflects this serializer's configuration, including PropertyNamingPolicy, SerializeDefaultValues, DeserializeDefaultValues, SerializeEnumValuesByName and any registered Converters.
Custom converters that do not override GetContract(ContractContext<TEncoder, TDecoder>) are described with an UndocumentedContract instead of a guess.
Exceptions
- NotSupportedException
Thrown when PreserveReferences is enabled, because reference preservation replaces repeated values with references in a way that cannot be described statically.
GetContract<T, TProvider>()
Describes the serialized form of a type in a format-neutral way.
public DataContract GetContract<T, TProvider>() where TProvider : IShapeable<T>
Returns
- DataContract
The contract describing how values of this type are written and read.
Type Parameters
TThe type to describe.
TProviderThe witness class that provides the shape for
T.
Remarks
The returned contract reflects this serializer's configuration, including PropertyNamingPolicy, SerializeDefaultValues, DeserializeDefaultValues, SerializeEnumValuesByName and any registered Converters.
Custom converters that do not override GetContract(ContractContext<TEncoder, TDecoder>) are described with an UndocumentedContract instead of a guess.
Exceptions
- NotSupportedException
Thrown when PreserveReferences is enabled, because reference preservation replaces repeated values with references in a way that cannot be described statically.
GetPath<TRoot, TValue>(Expression<Func<TRoot, TValue>>)
Translates an expression that walks CLR members into the ShapeShiftPath that locates the same value in a document this serializer reads or writes.
public ShapeShiftPath GetPath<TRoot, TValue>(Expression<Func<TRoot, TValue>> path) where TRoot : IShapeable<TRoot>
Parameters
pathExpression<Func<TRoot, TValue>>An expression whose single parameter is the root of the document, e.g.
person => person.Address.City.
Returns
- ShapeShiftPath
The path to the selected value, ready to pass to TryDeserializeFragment<T>(ref TDecoder, ShapeShiftPath, ITypeShape<T>, out T?, CancellationToken) or the
TrySeekdecoder extension member.
Type Parameters
TRootThe type at the root of the document.
TValueThe type of the value the expression selects.
Remarks
The path is resolved against this serializer's own contract for TRoot, so the
caller never has to know the serialized names: PropertyNamingPolicy and any
PropertyShapeAttribute.Name alias are applied for them, and a positionally encoded object
(such as a MessagePack array contract) contributes indexes instead of names.
The expression is only inspected — never compiled — so this is safe for trimming and NativeAOT.
These expression forms are supported:
- The parameter itself (
x => x), which is Root. - Member access, to any depth (
x => x.Address.City), including through the null-forgiving operator. - Constant, non-negative indexes into arrays and lists (
x => x.Tags[1]). - Constant string keys into string-keyed dictionaries (
x => x.Attributes["hue"]). - Value, and boxing or widening reference conversions, both of which are stepped over.
Anything else — computed indexes, method calls, narrowing or user-defined conversions, and dictionaries whose keys are not strings — is rejected rather than guessed at. Build a ShapeShiftPath directly for those, and for locations that only exist in a particular payload (an index chosen at runtime, or a property no .NET type declares).
Targeted deserialization keeps taking a ShapeShiftPath rather than gaining an expression
overload of its own on every input type. A fragment API is already generic over the fragment's type and
its shape provider; adding a root type and root shape provider to each one would multiply the overloads
on every serializer without expressing anything that
serializer.TryDeserializeFragment<City, Witness>(json, serializer.GetPath((Person p) => p.Address.City), out City? city)
does not already say. Computing the path once and reusing it is also cheaper, since a path is
immutable and independent of the payload.
Exceptions
- ArgumentException
Thrown when a step of
pathnames something that is not part of the serialized contract, such as a member the shape ignores or an extension-data member. The message quotes the failing step.- NotSupportedException
Thrown when a step of
pathuses a construct with no path equivalent (a method call, a computed index, or a conversion that changes which contract applies), or reaches a value whose representation is not described — for example one produced by a custom converter that does not override GetContract(ContractContext<TEncoder, TDecoder>). The message quotes the failing step. Also thrown when PreserveReferences is enabled, for the reason GetContract(ITypeShape) documents.
GetPath<TRoot, TValue>(Expression<Func<TRoot, TValue>>, ITypeShape<TRoot>)
Translates an expression that walks CLR members into the ShapeShiftPath that locates the same value in a document this serializer reads or writes.
public ShapeShiftPath GetPath<TRoot, TValue>(Expression<Func<TRoot, TValue>> path, ITypeShape<TRoot> typeShape)
Parameters
pathExpression<Func<TRoot, TValue>>An expression whose single parameter is the root of the document, e.g.
person => person.Address.City.typeShapeITypeShape<TRoot>The shape of
TRoot.
Returns
- ShapeShiftPath
The path to the selected value, ready to pass to TryDeserializeFragment<T>(ref TDecoder, ShapeShiftPath, ITypeShape<T>, out T?, CancellationToken) or the
TrySeekdecoder extension member.
Type Parameters
TRootThe type at the root of the document.
TValueThe type of the value the expression selects.
Remarks
The path is resolved against this serializer's own contract for TRoot, so the
caller never has to know the serialized names: PropertyNamingPolicy and any
PropertyShapeAttribute.Name alias are applied for them, and a positionally encoded object
(such as a MessagePack array contract) contributes indexes instead of names.
The expression is only inspected — never compiled — so this is safe for trimming and NativeAOT.
These expression forms are supported:
- The parameter itself (
x => x), which is Root. - Member access, to any depth (
x => x.Address.City), including through the null-forgiving operator. - Constant, non-negative indexes into arrays and lists (
x => x.Tags[1]). - Constant string keys into string-keyed dictionaries (
x => x.Attributes["hue"]). - Value, and boxing or widening reference conversions, both of which are stepped over.
Anything else — computed indexes, method calls, narrowing or user-defined conversions, and dictionaries whose keys are not strings — is rejected rather than guessed at. Build a ShapeShiftPath directly for those, and for locations that only exist in a particular payload (an index chosen at runtime, or a property no .NET type declares).
Targeted deserialization keeps taking a ShapeShiftPath rather than gaining an expression
overload of its own on every input type. A fragment API is already generic over the fragment's type and
its shape provider; adding a root type and root shape provider to each one would multiply the overloads
on every serializer without expressing anything that
serializer.TryDeserializeFragment<City, Witness>(json, serializer.GetPath((Person p) => p.Address.City), out City? city)
does not already say. Computing the path once and reusing it is also cheaper, since a path is
immutable and independent of the payload.
Exceptions
- ArgumentException
Thrown when a step of
pathnames something that is not part of the serialized contract, such as a member the shape ignores or an extension-data member. The message quotes the failing step.- NotSupportedException
Thrown when a step of
pathuses a construct with no path equivalent (a method call, a computed index, or a conversion that changes which contract applies), or reaches a value whose representation is not described — for example one produced by a custom converter that does not override GetContract(ContractContext<TEncoder, TDecoder>). The message quotes the failing step. Also thrown when PreserveReferences is enabled, for the reason GetContract(ITypeShape) documents.
GetPath<TRoot, TValue, TProvider>(Expression<Func<TRoot, TValue>>)
Translates an expression that walks CLR members into the ShapeShiftPath that locates the same value in a document this serializer reads or writes.
public ShapeShiftPath GetPath<TRoot, TValue, TProvider>(Expression<Func<TRoot, TValue>> path) where TProvider : IShapeable<TRoot>
Parameters
pathExpression<Func<TRoot, TValue>>An expression whose single parameter is the root of the document, e.g.
person => person.Address.City.
Returns
- ShapeShiftPath
The path to the selected value, ready to pass to TryDeserializeFragment<T>(ref TDecoder, ShapeShiftPath, ITypeShape<T>, out T?, CancellationToken) or the
TrySeekdecoder extension member.
Type Parameters
TRootThe type at the root of the document.
TValueThe type of the value the expression selects.
TProviderThe witness class that provides the shape for
TRoot.
Remarks
The path is resolved against this serializer's own contract for TRoot, so the
caller never has to know the serialized names: PropertyNamingPolicy and any
PropertyShapeAttribute.Name alias are applied for them, and a positionally encoded object
(such as a MessagePack array contract) contributes indexes instead of names.
The expression is only inspected — never compiled — so this is safe for trimming and NativeAOT.
These expression forms are supported:
- The parameter itself (
x => x), which is Root. - Member access, to any depth (
x => x.Address.City), including through the null-forgiving operator. - Constant, non-negative indexes into arrays and lists (
x => x.Tags[1]). - Constant string keys into string-keyed dictionaries (
x => x.Attributes["hue"]). - Value, and boxing or widening reference conversions, both of which are stepped over.
Anything else — computed indexes, method calls, narrowing or user-defined conversions, and dictionaries whose keys are not strings — is rejected rather than guessed at. Build a ShapeShiftPath directly for those, and for locations that only exist in a particular payload (an index chosen at runtime, or a property no .NET type declares).
Targeted deserialization keeps taking a ShapeShiftPath rather than gaining an expression
overload of its own on every input type. A fragment API is already generic over the fragment's type and
its shape provider; adding a root type and root shape provider to each one would multiply the overloads
on every serializer without expressing anything that
serializer.TryDeserializeFragment<City, Witness>(json, serializer.GetPath((Person p) => p.Address.City), out City? city)
does not already say. Computing the path once and reusing it is also cheaper, since a path is
immutable and independent of the payload.
Exceptions
- ArgumentException
Thrown when a step of
pathnames something that is not part of the serialized contract, such as a member the shape ignores or an extension-data member. The message quotes the failing step.- NotSupportedException
Thrown when a step of
pathuses a construct with no path equivalent (a method call, a computed index, or a conversion that changes which contract applies), or reaches a value whose representation is not described — for example one produced by a custom converter that does not override GetContract(ContractContext<TEncoder, TDecoder>). The message quotes the failing step. Also thrown when PreserveReferences is enabled, for the reason GetContract(ITypeShape) documents.
Serialize<T>(ref TEncoder, in T?, ITypeShape<T>, CancellationToken)
public void Serialize<T>(ref TEncoder encoder, in T? value, ITypeShape<T> typeShape, CancellationToken cancellationToken = default)
Parameters
encoderTEncodervalueTtypeShapeITypeShape<T>cancellationTokenCancellationToken
Type Parameters
T
TryDeserializeFragment<T>(ref TDecoder, ShapeShiftPath, ITypeShape<T>, out T?, CancellationToken)
Attempts to deserialize the value found at a given ShapeShiftPath, skipping over everything else in the document without fully parsing or buffering it.
public bool TryDeserializeFragment<T>(ref TDecoder decoder, ShapeShiftPath path, ITypeShape<T> typeShape, out T? value, CancellationToken cancellationToken = default)
Parameters
decoderTDecoderThe decoder, positioned wherever
pathshould be considered relative to (typically the start of the document).pathShapeShiftPathThe location of the value to deserialize.
typeShapeITypeShape<T>The shape of
T.valueTReceives the deserialized value if this method returns true; otherwise default.
cancellationTokenCancellationTokenA cancellation token.
Returns
Type Parameters
TThe type to deserialize the fragment as.
Remarks
After this method returns, the decoder's position is exactly as documented for the TrySeek decoder extension member
on which this method is built: on success, positioned at the start of the fragment's value's successor;
on failure, positioned immediately after whichever container could not produce the next step in the path.
Exceptions
- DecoderException
Thrown when a step along
pathexpects a map or vector but finds some other, non-null token.
WithReflectionConverterTypes(ConverterTypeCollection)
Creates a serializer configuration that activates converter types through reflection.
[RequiresDynamicCode("Activating converter types may require constructing closed generic converter types at runtime.")]
[RequiresUnreferencedCode("Converter constructors supplied as Type objects may be removed by trimming.")]
public ShapeShiftSerializer<TEncoder, TDecoder> WithReflectionConverterTypes(ConverterTypeCollection converterTypes)
Parameters
converterTypesConverterTypeCollectionThe converter types to activate.
Returns
- ShapeShiftSerializer<TEncoder, TDecoder>
A serializer configuration with reflection-based converter activation enabled.
Remarks
This opt-in is not trimming-safe or NativeAOT-safe unless every converter constructor is explicitly preserved. Prefer Converters or ConverterFactories in NativeAOT applications.