gwerren.com

Programming notes and tools...

Using A Parse Tree

Mon, 22 Jun 2020 JSON Data MapperC#

Posts in This Series

Introduction

I have started building a JSON data mapper for which I have first defined a simple mapping language.

I have built a tokenizer and parser for this language so the next step is to use the output of the parser to build the language executor.

The latest code is available as part of my JSuite GitHub project with the entry code file currently located here.

Requirements

The output of the parser is a set of mappings which should be applied one after the other to extract data from a source JSON object and map it onto a target JSON object.

In usage it is likely that the same mapping will be applied many times to different sets of source and target JSON objects. As a result we should generate a mapping executor once from the mapping script which can be applied to may sets of JSON data.

Execution in Two Parts

Each mapping statement is split into two main parts

Given this we can tackle each part individually. In my implementation I have defined the following two delegates to represent the two parts.

public delegate IEnumerable<ISourceMatch> ExtractFromSource(JObject sourceObject);

public delegate void UpdateTarget(JObject target, ISourceMatch source);

With ISourceMatch representing a single match in the source, being defined as:

public interface ISourceMatch
{
    JToken Element { get; }

    IReadOnlyDictionary<string, string> VariableValues { get; }
}

Given these definitions it is pretty simple to define a mapper that uses them:

private class MappingExecutor
{
    private readonly ExtractFromSource extract;
    private readonly UpdateTarget update;

    public MappingExecutor(IParseTree<TokenType, ParserRuleType> mappingDefinition)
    {
        this.extract = mappingDefinition.SourceExtractor();
        this.update = mappingDefinition.TargetUpdater();
    }

    public void Execute(JObject target, JObject source)
    {
        foreach (var extracted in this.extract(source))
            this.update(target, extracted);
    }
}

Generating the Execution Methods

The execution methods are both generated by building up a set of classes based on the parse tree. From each of these sets we then take a single entry method which provides the delegate implementation described above.

I don't intend to go through all the code here since you can see it all on GitHub, currently at:

Conclusion

This is the final step in building a JSON mapping language execution engine in C#.

By splitting the problem into the standard steps (tokenizing, parsing and using the parse tree), it became fairly simple to build up the full solution. At each stage further splitting the problem down into manageable chunks (a basic software development practice) makes each piece easy to reason about and build.