Skip to content

Commit bc166ce

Browse files
authored
Expand converter documentation with registration methods and priority (#137)
Rewrite site/docs/serialization/converters.md to document: - Three registration methods (member-level, options-level, type-level) - Priority order for converter resolution - Converter factory pattern with example - CurrentKey context access for custom converters - Precedence summary table including IParsable<T> fallback
1 parent 9f5e9ce commit bc166ce

1 file changed

Lines changed: 105 additions & 16 deletions

File tree

site/docs/serialization/converters.md

Lines changed: 105 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,55 +4,144 @@ title: Converters
44

55
Converters allow custom serialization/deserialization for specific CLR types.
66

7-
## Register a converter
7+
## Registration methods
88

9-
You can register converters globally via options:
9+
SharpYaml supports three ways to register converters, evaluated in this priority order:
10+
11+
1. **Member-level attribute** — highest priority, applies to a single property or field
12+
2. **Options-level** — applies globally to all types the converter can handle
13+
3. **Type-level attribute** — lowest priority of the three explicit registrations
14+
15+
If none of these match, SharpYaml falls through to built-in converters (primitives, collections, enums, etc.) and finally to the reflection-based object converter.
16+
17+
### Options-level registration
18+
19+
Register converters globally via [`YamlSerializerOptions.Converters`](xref:SharpYaml.YamlSerializerOptions.Converters). Converters are evaluated in order and take precedence over built-in converters:
1020

1121
```csharp
1222
var options = new YamlSerializerOptions
1323
{
1424
Converters =
1525
[
16-
new MyConverter(),
26+
new IPAddressConverter(),
27+
new TemperatureConverterFactory(),
1728
],
1829
};
1930
```
2031

21-
## Attribute-based converters
32+
### Type-level attribute
2233

23-
Use [`YamlConverterAttribute`](xref:SharpYaml.Serialization.YamlConverterAttribute) on a type or member:
34+
Apply [`YamlConverterAttribute`](xref:SharpYaml.Serialization.YamlConverterAttribute) to a type to associate a converter with all instances of that type:
2435

2536
```csharp
26-
using SharpYaml.Serialization;
37+
[YamlConverter(typeof(TemperatureConverter))]
38+
public readonly struct Temperature
39+
{
40+
public double Value { get; init; }
41+
public string Unit { get; init; }
42+
}
43+
```
44+
45+
### Member-level attribute
46+
47+
Apply [`YamlConverterAttribute`](xref:SharpYaml.Serialization.YamlConverterAttribute) to a property or field to override the converter for that specific member:
2748

28-
[YamlConverter(typeof(MyTypeConverter))]
29-
public sealed class MyType
49+
```csharp
50+
public sealed class Config
3051
{
52+
[YamlConverter(typeof(HexIntConverter))]
53+
public int Color { get; set; }
54+
55+
// Uses the default int converter
56+
public int Count { get; set; }
3157
}
3258
```
3359

3460
## Converter shape
3561

36-
Converters operate on [`YamlReader`](xref:SharpYaml.Serialization.YamlReader) and [`YamlWriter`](xref:SharpYaml.Serialization.YamlWriter):
62+
Converters extend [`YamlConverter<T>`](xref:SharpYaml.Serialization.YamlConverter`1) and operate on [`YamlReader`](xref:SharpYaml.Serialization.YamlReader) and [`YamlWriter`](xref:SharpYaml.Serialization.YamlWriter):
3763

3864
```csharp
39-
public sealed class MyIntConverter : YamlConverter<int>
65+
public sealed class TemperatureConverter : YamlConverter<Temperature>
4066
{
41-
public override int Read(YamlReader reader)
67+
public override Temperature Read(YamlReader reader)
4268
{
43-
// Read and advance the reader.
44-
reader.Skip();
45-
return 123;
69+
var text = reader.ScalarValue!;
70+
reader.Read();
71+
72+
var unit = text[^1..];
73+
var value = double.Parse(text[..^1], CultureInfo.InvariantCulture);
74+
return new Temperature { Value = value, Unit = unit };
4675
}
4776

48-
public override void Write(YamlWriter writer, int value)
49-
=> writer.WriteScalar("123");
77+
public override void Write(YamlWriter writer, Temperature value)
78+
=> writer.WriteScalar($"{value.Value.ToString("G", CultureInfo.InvariantCulture)}{value.Unit}");
79+
}
80+
```
81+
82+
## Converter factories
83+
84+
For open generic types or families of types, extend [`YamlConverterFactory`](xref:SharpYaml.Serialization.YamlConverterFactory):
85+
86+
```csharp
87+
public sealed class NullableConverterFactory : YamlConverterFactory
88+
{
89+
public override bool CanConvert(Type typeToConvert)
90+
=> Nullable.GetUnderlyingType(typeToConvert) is not null;
91+
92+
public override YamlConverter CreateConverter(Type typeToConvert, YamlSerializerOptions options)
93+
{
94+
var innerType = Nullable.GetUnderlyingType(typeToConvert)!;
95+
var converterType = typeof(NullableConverter<>).MakeGenericType(innerType);
96+
return (YamlConverter)Activator.CreateInstance(converterType)!;
97+
}
5098
}
5199
```
52100

101+
Register factories the same way as regular converters:
102+
103+
```csharp
104+
var options = new YamlSerializerOptions
105+
{
106+
Converters = [new NullableConverterFactory()],
107+
};
108+
```
109+
53110
## Reader and writer basics
54111

55112
- [`YamlReader`](xref:SharpYaml.Serialization.YamlReader) is positioned on a token; converters must consume the current value and advance the reader.
56113
- [`YamlWriter`](xref:SharpYaml.Serialization.YamlWriter) writes YAML in a streaming manner; converters should write a complete value (scalar/sequence/mapping).
57114

58115
For most custom scenarios, prefer writing scalars (`writer.WriteScalar(...)`) unless you need to emit complex YAML structures.
116+
117+
## Accessing the current mapping key
118+
119+
[`YamlReader.CurrentKey`](xref:SharpYaml.Serialization.YamlReader.CurrentKey) exposes the most recent mapping key set by built-in dictionary and object converters. Custom converters can use this for context-dependent logic:
120+
121+
```csharp
122+
public sealed class KeyAwareConverter : YamlConverter<string>
123+
{
124+
public override string? Read(YamlReader reader)
125+
{
126+
var text = reader.ScalarValue ?? string.Empty;
127+
reader.Read();
128+
129+
// Replace placeholder with the dictionary key or property name
130+
return text.Replace("${KEY}", reader.CurrentKey ?? string.Empty);
131+
}
132+
133+
public override void Write(YamlWriter writer, string? value)
134+
=> writer.WriteScalar(value ?? string.Empty);
135+
}
136+
```
137+
138+
## Precedence summary
139+
140+
| Source | Scope | Priority |
141+
| --- | --- | --- |
142+
| `[YamlConverter]` on member | Single property/field | Highest |
143+
| `YamlSerializerOptions.Converters` | All matching types | High |
144+
| `[YamlConverter]` on type | All instances of type | Medium |
145+
| Built-in converters | Primitives, collections, enums, etc. | Low |
146+
| `IParsable<T>` fallback (.NET 7+) | Types implementing `IParsable<T>` | Lower |
147+
| Object converter | Any remaining type | Lowest |

0 commit comments

Comments
 (0)