Get current context from within node deserializer

Summary

A deserializer failed to access property-level context during YAML parsing, preventing it from detecting whether a target property had a required attribute. The serializer path exposed IPropertyDescriptor, but the deserializer path did not, causing asymmetric behavior and unexpected type‑conversion failures.

Root Cause

The underlying issue was that YamlDotNet’s deserialization pipeline does not surface property metadata to custom INodeDeserializer implementations. Unlike serialization, the deserializer receives only:

  • The parser state (IParser)
  • The expected CLR type
  • The nested object deserializer delegate
  • The root deserializer

None of these provide the declaring property, so attribute‑based opt‑in logic cannot be applied.

Why This Happens in Real Systems

This pattern appears in many serialization frameworks because:

  • Deserialization is type‑driven, not property‑driven. The framework knows the type it must produce, not the property it will be assigned to.
  • Node deserializers operate before property binding, so they cannot know which property is being populated.
  • Pipelines are asymmetric: serialization walks the object graph (properties → values), while deserialization walks the YAML graph (nodes → objects).

Real-World Impact

When property context is missing:

  • Attribute-based behaviors silently fail, because the deserializer cannot detect them.
  • Type coercion becomes unreliable, especially when mapping flexible YAML structures into strict CLR types.
  • Developers implement workarounds that are brittle or require reflection hacks.
  • Configuration files become harder to validate, leading to runtime surprises.

Example or Code (if necessary and relevant)

A correct approach requires hooking into the type inspector, not the node deserializer. The type inspector does know the property being assigned.

public class MyTypeInspector : TypeInspectorSkeleton
{
    private readonly ITypeInspector _inner;

    public MyTypeInspector(ITypeInspector inner)
    {
        _inner = inner;
    }

    public override IEnumerable GetProperties(Type type, object? container)
    {
        foreach (var prop in _inner.GetProperties(type, container))
        {
            if (prop.GetCustomAttribute() != null)
            {
                prop.TypeOverride = typeof(MySpecialType);
            }

            yield return prop;
        }
    }
}

This shifts the decision to the property discovery phase, where metadata is available.

How Senior Engineers Fix It

Experienced engineers solve this by restructuring the pipeline:

  • Move attribute‑based logic into a custom ITypeInspector, where property metadata is available.
  • Override TypeOverride to influence which type the deserializer should instantiate.
  • Avoid custom INodeDeserializer unless absolutely necessary, because it lacks property context.
  • Use composition: wrap the default inspector and selectively modify only the properties that require special handling.
  • Keep deserializers stateless, letting the type system drive behavior.

Why Juniors Miss It

Less experienced developers often assume:

  • Serialization and deserialization are symmetric, so they expect the same metadata to be available.
  • Node deserializers have full context, when in reality they operate at a lower level.
  • Attributes can be checked anywhere, not realizing that metadata is lost once the pipeline enters node-level parsing.
  • The first extension point they find is the right one, instead of tracing the full pipeline.

If you want, I can extend this into a full engineering post with diagrams explaining the YAML → CLR binding pipeline.

Leave a Comment