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?
Parser does has a Peek function, which is marked as obsolete, but has been replaced with an Accept function which does basically the same thing with an out parameter instead. However, the easiest thing to do would be to just switch on parser.Current
:
configs = parser.Current switch
{
MappingStart => serializer.Deserialize<Dictionary<string, T>>(parser)
.Select(kvp =>
{
kvp.Value.Name = kvp.Key;
return kvp.Value;
}),
SequenceStart => serializer.Deserialize<List<T>>(parser),
_ => null
};
Make sure to drop the parser.Consume
call before this.