There's a YAML configuration file that my application loads:
sonarr:
- base_url: abc1
api_key: xyz1
- base_url: abc2
api_key: xyz2
I want to change the schema for this to use a mapping (for named instances) rather than an array. Additionally, I want to continue to support array-style (with a deprecation message) for backward compatibility. So the sonarr
content can either be a mapping or a sequence. The new schema would look like this:
sonarr:
instance1:
base_url: abc1
api_key: xyz1
instance2:
base_url: abc2
api_key: xyz2
I've spent hours googling and trying different solutions. Nothing seems to work. The approach I was trying was something like this:
public IEnumerable<T> LoadFromStream(TextReader stream, string configSection)
{
var parser = new Parser(stream);
parser.Consume<StreamStart>();
parser.Consume<DocumentStart>();
parser.Consume<MappingStart>();
var validConfigs = new List<T>();
while (parser.TryConsume<Scalar>(out var key))
{
if (key.Value != configSection)
{
parser.SkipThisAndNestedEvents();
continue;
}
var evt = parser.Consume<NodeEvent>();
var configs = evt switch
{
SequenceStart => _deserializer.Deserialize<Dictionary<string, T>>(parser)
.Select(kvp =>
{
kvp.Value.Name = kvp.Key;
return kvp.Value;
})
.ToList(),
MappingStart => _deserializer.Deserialize<List<T>>(parser),
_ => null
};
if (configs is not null)
{
ValidateConfigs(configSection, configs, validConfigs);
}
parser.SkipThisAndNestedEvents();
}
return validConfigs;
}
However, this won't work because the Consume
and TryConsume
methods eat the MappingStart
/ SequenceStart
nodes, which makes it impossible to deserialize using List/Dictionary. I think to make this work I need a Consume
that is more like a peek.
How should I go about handling this situation, or more generally, flexible schemas like this?