Table of Contents

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

TEncoder

The type of encoder to use.

TDecoder

The 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

DeserializeDefaultValuesPolicy

InternStrings

Gets a value indicating whether to intern strings during deserialization.

public bool InternStrings { get; init; }

Property Value

bool

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

SerializeDefaultValuesPolicy

SerializeEnumValuesByName

Gets a value indicating whether enum values are serialized by name instead of by ordinal.

public bool SerializeEnumValuesByName { get; init; }

Property Value

bool

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

typeShape ITypeShape<T>

The shape of T.

cancellationToken CancellationToken

A 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

T

The 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

typeShape ITypeShape<T>

The shape of T.

cancellationToken CancellationToken

A 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

T

The 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

provider ITypeShapeProvider

The 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 GeneratedTypeShapeProvider static 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.

cancellationToken CancellationToken

A 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

decoder TDecoder

The decoder, positioned wherever path should be considered relative to (typically the start of the document).

path ShapeShiftPath

The location of the value to deserialize.

typeShape ITypeShape<T>

The shape of T.

cancellationToken CancellationToken

A cancellation token.

Returns

T

The deserialized value.

Type Parameters

T

The type to deserialize the fragment as.

Exceptions

ShapeShiftSerializationException

Thrown when path could not be found.

DecoderException

Thrown when a step along path expects 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

decoder TDecoder
typeShape ITypeShape<T>
cancellationToken CancellationToken

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

typeShape ITypeShape

The 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

T

The 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

T

The type to describe.

TProvider

The 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

path Expression<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 TrySeek decoder extension member.

Type Parameters

TRoot

The type at the root of the document.

TValue

The 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 path names 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 path uses 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

path Expression<Func<TRoot, TValue>>

An expression whose single parameter is the root of the document, e.g. person => person.Address.City.

typeShape ITypeShape<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 TrySeek decoder extension member.

Type Parameters

TRoot

The type at the root of the document.

TValue

The 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 path names 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 path uses 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

path Expression<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 TrySeek decoder extension member.

Type Parameters

TRoot

The type at the root of the document.

TValue

The type of the value the expression selects.

TProvider

The 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 path names 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 path uses 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

encoder TEncoder
value T
typeShape ITypeShape<T>
cancellationToken CancellationToken

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

decoder TDecoder

The decoder, positioned wherever path should be considered relative to (typically the start of the document).

path ShapeShiftPath

The location of the value to deserialize.

typeShape ITypeShape<T>

The shape of T.

value T

Receives the deserialized value if this method returns true; otherwise default.

cancellationToken CancellationToken

A cancellation token.

Returns

bool

true if path was found and value was populated; false otherwise.

Type Parameters

T

The 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 path expects 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

converterTypes ConverterTypeCollection

The 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.