TypeShape providers

This document provides a walkthrough of the built-in TypeShape providers. These are typically consumed by end users looking to use their types with libraries built on top of the TypeShape core abstractions.

Source Generator

We can use the built-in source generator to auto-generate shape metadata for a user-defined type like so:

using TypeShape;

[GenerateShape]
partial record Person(string name, int age, List<Person> children);

This augments Person with an explicit implementation of ITypeShapeProvider<Person>, which is used as an entrypoint by libraries targeting TypeShape:

MyRandomGenerator.Generate<Person>(); // Compiles

public static class MyRandomGenerator
{
    public static T Generate<T>(int seed = 0) where T : ITypeShapeProvider<T>;
}

The source generator also supports shape generation for third-party types using witness types:

[GenerateShape<Person[]>]
[GenerateShape<List<int>>]
public partial class Witness; // : ITypeShapeProvider<Person[]>, ITypeShapeProvider<List<int>>

which can be applied against supported libraries like so:

MyRandomGenerator.Generate<Person[], Witness>() // Compiles
MyRandomGenerator.Generate<List<int>, Witness>() // Compiles

public static class MyRandomGenerator
{
    public static T Generate<T, TWitness>(int seed = 0) where TWitness : ITypeShapeProvider<T>;
}

Reflection Provider

TypeShape includes a reflection-based provider that resolves shape metadata at run time:

using TypeShape.ReflectionProvider;

ITypeShapeProvider provider = ReflectionTypeShapeProvider.Default;
var shape = (ITypeShape<Person>)provider.GetShape(typeof(Person));

Which can be consumed by supported libraries as follows:

MyRandomGenerator.Generate<Person>(ReflectionTypeShapeProvider.Default);
MyRandomGenerator.Generate<Person[][]>(ReflectionTypeShapeProvider.Default);
MyRandomGenerator.Generate<List<int>>(ReflectionTypeShapeProvider.Default);

public static class MyRandomGenerator
{
    public static T Generate<T>(ITypeShapeProvider provider);
}

By default, the reflection provider uses dynamic methods (Reflection.Emit) to speed up reflection, however this might not be desirable when running in certain platforms (e.g. blazor-wasm). It can be turned off using the relevant constructor parameter:

ITypeShapeProvider provider = new ReflectionTypeShapeProvider(useReflectionEmit: false);

Shape attributes

TypeShape exposes a number of attributes that tweak aspects of the generated shape. These attributes are recognized both by the source generator and the reflection provider.

PropertyShapeAttribute

Configures aspects of a generated property shape, for example:

class UserData
{
    [PropertyShape(Name = "id", Order = 0)]
    public required string Id { get; init; }

    [PropertyShape(Name = "name", Order = 1)]
    public string? Name { get; init; }

    [PropertyShape(Ignore = true)]
    public string? UserSecret { get; init; }
}

Compare with System.Runtime.Serialization.DataMemberAttribute and Newtonsoft.Json.JsonPropertyAttribute.

ConstructorShapeAttribute

Can be used to pick a specific constructor for a given type, if there is ambiguity:

class PocoWithConstructors
{
    public PocoWithConstructors();
    [ConstructorShape] // <--- Only use this constructor in TypeShape apps
    public PocoWithConstructors(int x1, int x2);
}

Compare with System.Text.Json.Serialization.JsonConstructorAttribute.

ParameterShapeAttribute

Configures aspects of a constructor parameter shape:

class PocoWithConstructors
{
    public PocoWithConstructors([ParameterShape(Name = "name")] string x1, [ParameterShape(Name = "age")] int x2);
}