Legivel


1: 
open System.Collections.Generic

Legivel Tutorial

Yaml comes with a technical challenge; it supports structures which are not suported in F# and C#. For example in Yaml you can create a list where each element is of a different type. List<obj> is seen as an undesired type - we wish static types, which are the main target for this library.

Legivel provides an F#-idiomatic Yaml to Native conversion. This simply means that it does not support all Yaml structures. This tutorial demonstrates all supported mappings. Note that prerequisite statements as #I/#r/open are left out of these examples.

Primitive mapping

Consider the folowing examples, which map a Yaml scalar to a primitive:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
Deserialize<int> "1"

Deserialize<float> "3.14"

Deserialize<bool> "True"

Deserialize<DateTime> "2014-09-12"

List mapping

In the examples below, an integer list is parsed. However you can use any supported type as list element.

1: 
Deserialize<int list> "[ 1, 1, 2, 3, 5, 8, 13 ]"

Which results in:

[Succes {Data = [1; 1; 2; 3; 5; 8; 13];
         Warn = [];}]

Yaml flow style:

1: 
2: 
3: 
4: 
5: 
Deserialize<int list> "
- 1
- 2
- 3
"

Which results in:

[Succes {Data = [1; 2; 3];
         Warn = [];}]

Record mapping

In the example below, Yaml from example 2.4 is mapped to a record type. You can use attributes in the type definition, if the field name in Yaml is different than the record field name.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
open Legivel.Attributes

type PlayerStats = {
    name    : string
    hr      : int
    [<YamlField("avg")>] average : float
}

let yaml = "
-
  name: Mark McGwire
  hr:   65
  avg:  0.278
-
  name: Sammy Sosa
  hr:   63
  avg:  0.288"

Deserialize<PlayerStats list> yaml

Which results in:

[Succes {Data = [{name = "Mark McGwire";
                  hr = 65;
                  average = 0.278;}; {name = "Sammy Sosa";
                                      hr = 63;
                                      average = 0.288;}];
         Warn = [];}]

Option mapping

In the example below, an option is parsed. When a value is available, it is mapped to Some(data). If the value is absent, it is mapped to None.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
type OptionExample = {
  opt1 : int option
  opt2 : int option
}

let yaml = "opt1: 31"

Deserialize<OptionExample> yaml

Which results in:

[Succes {Data = {opt1 = Some 31;
                 opt2 = None;};
         Warn = [];}]

Also "null" is translated to None:

1: 
2: 
3: 
let yaml = "{ opt1: 31, opt2: null }"

Deserialize<OptionExample> yaml

Which results in:

[Succes {Data = {opt1 = Some 31;
                 opt2 = None;};
         Warn = [];}]

Discriminated Union mapping

Discriminated unions can be compiled to a C# enum, or to an ordinary DU. They can also be appear as a value in Yaml (plain style), or one key/value pair in a mapping determines both Union Case and contained data (embedded style). You can also use an attribute to customize the yaml-to-union-case mapping.

Below an example of plain-style yaml which maps to a enum-DU:

1: 
2: 
3: 
4: 
5: 
6: 
type UnionCaseEnum =
    |   One=1
    |   [<YamlValue("two")>] Two=2

let yaml = "two # alias"
Deserialize<UnionCaseEnum> yaml

Which results in:

[Succes {Data = Two;
         Warn = [];}]

The following example demonstrates embedded style yaml which maps to a Union Case with record data:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
type SomeData = {
    Name : string
    Age  : int
}

[<YamlField("TypeOf")>]
type UnionCaseWithData =
    |   One of SomeData
    |   [<YamlValue("two")>] Two of SomeData

let yaml = "
    Name: 'Frank'
    Age:  43
    TypeOf : One
"

Deserialize<UnionCaseWithData> yaml

Which results in:

[Succes {Data = One {Name = "Frank";
                     Age = 43;};
         Warn = [];}]

Map mapping

Yaml mapping Nodes can be converted to an FSharp Map type as follows:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
type ProductType =
    |   Zero=0      
    |   Light=1
    |   [<YamlValue("normal")>] Normal=2


type Contents = {
    Name     : string
    SugarMg  : int
}

let yaml = "
    Zero  : { Name: 'RefreshBubbles No Sugar', SugarMg:  10 }
    Light : { Name: 'RefreshBubbles Light', SugarMg:  40 }
    normal : { Name: 'RefreshBubbles Heroes', SugarMg:  150 }
"

Deserialize<Map<ProductType,Contents>> yaml

Which results in:

[Succes
   {Data =
     map
       [(Zero, {Name = "RefreshBubbles No Sugar";
                SugarMg = 10;}); (Light, {Name = "RefreshBubbles Light";
                                          SugarMg = 40;});
        (Normal, {Name = "RefreshBubbles Heroes";
                  SugarMg = 150;})];
    Warn = [];}]

Map IDictionary

Yaml mapping nodes can be mapped to an IDictionary<'TKey,'TValue>, with the constraint that the target type has a parameterless constructor.

1: 
Deserialize<Dictionary<ProductType,Contents>> yaml

Which results in:

[Succes
   {Data =
     seq
       [[Zero, {Name = "RefreshBubbles No Sugar";
 SugarMg = 10;}];
        [Light, {Name = "RefreshBubbles Light";
 SugarMg = 40;}];
        [Normal, {Name = "RefreshBubbles Heroes";
 SugarMg = 150;}]];
    Warn = [];}]
Fork me on GitHub