Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I cannot figure out how to parse a YAML file with a key like "[0 cm, 1 cm]:" that can change #471

Closed
killsap opened this issue Feb 20, 2020 · 4 comments

Comments

@killsap
Copy link

killsap commented Feb 20, 2020

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?

@killsap
Copy link
Author

killsap commented Feb 20, 2020

The indentation is missing from the above post. Here is a better example of the indentation:
issueyaml

@aaubry
Copy link
Owner

aaubry commented Feb 20, 2020

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

@killsap
Copy link
Author

killsap commented Feb 20, 2020

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!

@EdwardCooke
Copy link
Collaborator

There’s been no comments in over 4 years. Closing this. FYI, a custom type emitter could probably be used for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants