YamlDotNet
YamlDotNet copied to clipboard
I cannot figure out how to parse a YAML file with a key like "[0 cm, 1 cm]:" that can change
I cannot figure out how to parse a YAML file with this structure:
HeightSensor: [0 cm, 1 cm]: - - set - HeightSensor - frequency: 10 sec
The "[0 cm, 1 cm]:" is super problematic. Can anyone help me with the C# struct/class I need to represent this so I can Deserialize the YAML file? Or if there is a better way? These number values can change so how do I parse a key with a variable name like this?
The indentation is missing from the above post. Here is a better example of the indentation:
Without further details, it is not clear what would be the correct model to represent your data. One possible approach would be to define a custom type to represent [0 cm, 1 cm]. Here I will assume that this should be interpreted as a range of lengths and define two classes to represent that:
// This assumes that exactly two lengths will be specified in a list.
// An alternative would be to just use a List<Length>
class Range
{
public Length Start { get; set; }
public Length End { get; set; }
}
class Length
{
public int Value { get; set; }
public string Units { get; set; }
}
By default YamlDotNet would represent each of these classes as mappings, so we will need to change that behaviour. One way is to implement IYamlConvertible on each of the classes.
Let's start with the Length class. The representation used is a plain scalar (a string) with the value and units separated by a space:
class Length : IYamlConvertible
{
public int Value { get; set; }
public string Units { get; set; }
void IYamlConvertible.Read(IParser parser, Type expectedType,
ObjectDeserializer nestedObjectDeserializer)
{
var scalar = parser.Consume<Scalar>();
var parts = scalar.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries);
this.Value = int.Parse(parts[0]);
this.Units = parts[1];
}
void IYamlConvertible.Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer)
{
emitter.Emit(new Scalar($"{Value} {Units}"));
}
}
Then the Range class. Here we'll assume that a range is always represented by a list of two Length. Alternatively, we could have used List<Length> which would allow any number of values.
class Range : IYamlConvertible
{
public Length Start { get; set; }
public Length End { get; set; }
void IYamlConvertible.Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer)
{
parser.Consume<SequenceStart>();
this.Start = (Length)nestedObjectDeserializer(typeof(Length));
this.End = (Length)nestedObjectDeserializer(typeof(Length));
parser.Consume<SequenceEnd>();
}
void IYamlConvertible.Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer)
{
emitter.Emit(new SequenceStart(null, null, false, SequenceStyle.Flow));
nestedObjectSerializer(this.Start);
nestedObjectSerializer(this.End);
emitter.Emit(new SequenceEnd());
}
}
With the above types defined, we are able to parse the YAML as follows:
class Model
{
public Dictionary<Range, object> HeightSensor { get; set; }
}
public static void Main()
{
var yaml = @"
HeightSensor:
[0 cm, 1 cm]:
- - set
- HeightSensor
- frequency: 10 sec
";
var deserializer = new DeserializerBuilder()
.Build();
var model = deserializer.Deserialize<Model>(yaml);
}
Since your question was focused on parsing the [0 cm, 1 cm] part, I have declared the value of the dictionary as object. You will need to define a proper model for that part. I can help you if needed, but that will require more details on the meaning of that YAML.
Here you can see the fully working code:
https://dotnetfiddle.net/2YoXOg
First off, I really appreciate this. It works perfectly! I am parsing the entire YAML file, that is in the zip folder attached.
The issue I am having now is when I Serialize the class, with new data, the output is not in the same form as the original file. I want to edit values and write the file to a .yml file without all the new '?' characters, extra values in the START and END keys, and the "String:" key after "- -".
My classes with your implementation used in MONITOR:
`public class ConfigFile { public StartEndAndInner[][] START { get; set; } public Monitor MONITOR { get; set; } public StartEndAndInner[][] END { get; set; } }
public class StartEndAndInner
{
public ValUnit intensity { get; set; }
public ValUnit duration { get; set; }
public ValUnit frequency { get; set; }
public string String;
public static implicit operator StartEndAndInner(string String) => new StartEndAndInner { String = String };
}
public class Monitor
{
public Dictionary<Time, object> Time { get; set; }
public Dictionary<Range, object> HeightSensor { get; set; }
public Dictionary<Range, object> MoistureSensor { get; set; }
}
// This assumes that exactly two lengths will be specified in a list.
// An alternative would be to just use a List<ValUnit>
public class Range : IYamlConvertible
{
public ValUnit Start { get; set; }
public ValUnit End { get; set; }
void IYamlConvertible.Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer)
{
parser.Consume<SequenceStart>();
this.Start = (ValUnit)nestedObjectDeserializer(typeof(ValUnit));
this.End = (ValUnit)nestedObjectDeserializer(typeof(ValUnit));
parser.Consume<SequenceEnd>();
}
void IYamlConvertible.Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer)
{
emitter.Emit(new SequenceStart(null, null, false, SequenceStyle.Flow));
nestedObjectSerializer(this.Start);
nestedObjectSerializer(this.End);
emitter.Emit(new SequenceEnd());
}
public override string ToString()
{
return string.Format("({0} -> {1})", Start, End);
}
}
public class ValUnit : IYamlConvertible
{
public string Value { get; set; }
public string Units { get; set; }
void IYamlConvertible.Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer)
{
var scalar = parser.Consume<Scalar>();
var parts = scalar.Value.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
this.Value = parts[0];
this.Units = parts[1];
}
void IYamlConvertible.Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer)
{
emitter.Emit(new Scalar(string.Format("{0} {1}", Value, Units)));
}
public override string ToString()
{
return string.Format("{0} [{1}]", Value, Units);
}
}
// This assumes only one value in []
public class Time : IYamlConvertible
{
public ValUnit Start { get; set; }
void IYamlConvertible.Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer)
{
parser.Consume<SequenceStart>();
this.Start = (ValUnit)nestedObjectDeserializer(typeof(ValUnit));
parser.Consume<SequenceEnd>();
}
void IYamlConvertible.Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer)
{
emitter.Emit(new SequenceStart(null, null, false, SequenceStyle.Flow));
nestedObjectSerializer(this.Start);
emitter.Emit(new SequenceEnd());
}
public override string ToString()
{
return string.Format("({0})", Start);
}
}`
The output when I serialize is output.txt in zip zip.zip Again, I really appreciate the help!