danielwertheim

danielwertheim


notes from a passionate developer

Developer that lives by the mantra "code is meant to be shared".

Share


Tags


Disclaimer

This is a personal blog. The opinions expressed here represent my own and not those of my employer, nor current or previous. All content is published "as is", without warranty of any kind and I don't take any responsibility and can't be liable for any claims, damages or other liabilities that might be caused by the content.

Structurizer - object graph to key-values

Daniel WertheimDaniel Wertheim

Structurizer is a small project that initially comes from an old project of mine, SisoDB. In there it was used to assist the indexer to index objects in a database. The idea is that it builds a meta-model for your object type and uses IL to extract and build a key-value representation of it. OK, sounds nice, but what can this be used for? Indexing, logging, checksum generation ... and other situations where you need a generic key-value representation of your objects.

Step 1 - Install

NuGet is your friend here (I hope) even though it works in mysterious ways sometimes.

install-package structurizer  

Step 2 - Define a model

Lets keep this simple and go with an simplified Order model.

public class Order  
{
  public int Id { get; set; }
  public string OrderNo { get; set; }
  public DateTime PlacedAt { get; set; }
  public List<OrderLine> Lines { get; set; }
}

public class OrderLine  
{
  public string ArticleNo { get; set; }
  public int Qty { get; set; }
  public List<Prop> Props { get; set; }
}

public class Prop  
{
  public string Name { get; set; }
  public string Value { get; set; }
}

Step 3 - Configure types in Structurizer

Structurizer uses a StructureBuilder to create one Structure per object-instance being passed. For the builder to work, you need to register the type in StructureTypeConfigurations.

var typeConfigs = new StructureTypeConfigurations();  
typeConfigs.Register<Order>();  

This will by default create StructureIndexes for all public properties of the object. To limit you can either Include or Exclude members.

Including

typeConfigs.Register<Order>(cfg => cfg  
  .UsingIndexMode(IndexMode.Inclusive)
  .Members(i => i.OrderNo)
  .Members(i => i.Lines[0].ArticleNo)
  .Members(i => i.Lines[0].Qty));

Excluding

typeConfigs.Register<Order>(cfg => cfg  
  .UsingIndexMode(IndexMode.Exclusive)
  .Members(i => i.Lines[0].Props));

Step 4 - Create the builder

The StructureBuilder is what actually creates the Structures that contains the StructureIndexes. To be able to create a Structure it needs the TypeConfigurations.

Please, please, please, understand that you should really keep the StructureBuilder around as it caches information about the meta model for your types. So don't re-create the builder all the time.

var structureBuilder = StructureBuilder.Create(typeConfigs);  

Step 5 - Create Structures with Structure indexes

Use the StructureBuilder and just call CreateStructure or if you have a collection of objects, CreateStructures.

Please note that any extracted NullValues will not be returned. So Null is not seen as a value.

var orderStructure = structureBuilder.CreateStructure(order);  
DumpStructure(orderStructure);  
private static void DumpStructure(IStructure structure)  
{
  Console.WriteLine($"===== {structure.Name} =====");
  foreach (var index in structure.Indexes)
    Console.WriteLine(DefaultIndexValueFormatter.Format(index));
}

Each StructureIndes contains:

These can be used to e.g. format how values should be output or control how values should be indexed or something. Lets do some simple formatting.

public static class DefaultIndexValueFormatter  
{
  public static string Format(IStructureIndex index)
  {
    switch (index.DataTypeCode)
    {
      case DataTypeCode.String:
      case DataTypeCode.Guid:
      case DataTypeCode.Enum:
        return $"Path\t{index.Path}=\"{index.Value}\"";
      case DataTypeCode.DateTime:
        return $"Path\t{index.Path}=\"{((DateTime)index.Value):O}\"";
      default:
        return $"Path\t{index.Path}={index.Value}";
    }
  }
}

The output, running using

typeConfigs.Register<Order>(cfg => cfg  
  .UsingIndexMode(IndexMode.Exclusive)
  .Members(i => i.Lines[0].Props));

will be:

===== Order =====
Path  Id=1  
Path  OrderNo="2016-1234"  
Path  PlacedAt="2016-11-13T16:56:36.3679890+01:00"  
Path  Lines[0].ArticleNo="Article-Line0"  
Path  Lines[1].ArticleNo="Article-Line1"  
Path  Lines[0].Qty=42  
Path  Lines[1].Qty=3  

Each StructureIndex also has a Name property. The difference between Name and Path is only visible when there's an index for a value in a path, steeming from something that is enumerable:

"Name":   "Lines.ArticleNo"
"Path":   "Lines[0].ArticleNo"
"Value":  "Article-Line0"

That's it. Feel free to use it. It's MIT licensed.

Cheers,

//Daniel

Developer that lives by the mantra "code is meant to be shared".

Comments