Skip to content

Document Conversion

Anton Chaplygin edited this page Sep 16, 2019 · 17 revisions

Getting started

Suppose we need to convert the class Source to the class Dest.

public class Source
{
    public string Email { get; set; }

    public string UserId { get; set; }

    public Tuple<string, string>[] Properties { get; set; }
}

public class Dest
{
    public string Login { get; set; }

    public Guid UserId { get; set; }

    public KeyValuePair<string, string>[] Properties { get; set; }
}

We should create a class derived from the abstract class ConverterCollection<TSource, TDest>, which exposes a public method GetConverter that returns Func<TSource, TDest> – desired conversion function. Then we are expected to provide an implementation for abstract method Configure. This method should contain conversion rules, which could be set using the configurator parameter.

public class ExampleConverterCollection : ConverterCollection<Source, Dest>
{
    public UserConverterCollection(IPathFormatterCollection pathFormatterCollection, 
                                   IStringConverter stringConverter)
        : base(pathFormatterCollection, stringConverter)
    {
    }

    protected override void Configure(MutatorsContext context, 
                                      ConverterConfigurator<Source, Dest> configurator)
    {
        configurator.Target(dest => dest.Login)
                    .Set(source => source.Email);

        configurator.Target(dest => dest.UserId)
                    .Set(source => Guid.Parse(source.UserId));

        configurator.Target(dest => dest.Properties.Each().Key)
                    .Set(source => source.Properties.Current().Item1);
        configurator.Target(dest => dest.Properties.Each().Value)
                    .Set(source => source.Properties.Current().Item2);
    }
}

We use the configurator's method Target to provide a path to a property, to which we want to set a value. The Set method is used to provide a function returning a value to set. The Target method takes as a parameter a lambda expression with a single argument of type Dest, while Set method takes a lambda expression with a parameter of type Source. Note that the Target must be called before the Set method.

Array conversion

As you can see in the example above we use the Each method to mark the destination array inside the lambda of the Target method and we use the Current method inside the Set method to mark the source enumerable for the conversion. This construction tells us that the kth item of the destination array will receive a value from the kth item of the source array. These methods are extensions methods that take an Enumerable<T> and return a value of T, so you can continue to write a path to a property of an arra item. They are not invoked during the execution of conversion function, but serve as substitues for corresponding indices of the source array and the destination array.

If you need to convert an array that is inside of an item of another array you simply should call Each and Current methods after going through each property of an array type. For example:

configurator.Target(dest => dest.Users.Each().Properties.Each().Value)
            .Set(source => source.UserInfos.Current().Values.Current());

If method

A conversion rule can depend on a condition involving some TSource properties. Mutators allow to define such conversions using the If method:

configurator.If(source => source.IsLegalEntity)
            .Target(dest => dest.TaxNumber)
            .Set(source => source.LegalEntityInfo.Inn);

configurator.If(source => !source.IsLegalEntity)
            .Target(dest => dest.TaxNumber)
            .Set(source => source.IndividualEntrepreneurInfo.Inn);

The If method takes a condition represented by a lambda expression that returns a bool? value. The method returns a new ConverterConfigurator instance that contains information about the applied condition. All conversions created using this instance will happen if and only if the condition is satisfied. If the If method is applied multiple times, a conversion will happen if and only if all conditions passed to called If methods are satisfied.

Goto method

Goto is a method which helps to get rid of a boilerplate code. Instead of writing long and slightly different paths from the root of the document, you can travel to a node of the document tree and define conversion rules for a subtree of this node.

var valuesConfigurator = configurator.GoTo(dest => dest.Properties.Each(), 
                                           source => source.Properties.Current());
valuesConfigurator.Target(dest => dest.Key)
                  .Set(source => source.Item1);
valuesConfigurator.Target(dest => dest.Key)
                  .Set(source => source.Item2);

Goto method returns a new ConverterConfigurator instance, which "remembered" a path to a node of document tree. All configurator methods called on the new instance will take as parameters paths going from the corresponding document subtree root. The example above shows how the Properties array conversion can be rewritten using the Goto method.

Other methods

The ConverterConfigurator class also has some handy overloads for the Set method alongside with other useful methods defined in ConverterConfiguratorExtensions.

ConverterCollection constructor parameters.

PathFormatterCollection parameter

The PathFormatterCollection parameter is irrelevant in the context of a simple document conversion, so you can simply pass a new PathFormatterCollection as the constructor parameter. It is used when a field or a property you convert requires a preliminary validation (see [TODO: link to validation before conversion]).

StringConverter parameter

The StringConverter parameter is used only for a custom fields conversion, so if you don't use custom fields, you can simply pass null as the corresponding constructor parameter.

MutatorsContext

The MutatorsContext is a parameter of the Configure method, which is used to customize a conversion ruleset. See more on MutatorsContext page.

More examples

You can find more conversion examples in converter collections in functional tests.