JTran is a .Net Standard Library for doing JSON to JSON transformations.
JTran is heavily influenced by XSLT but whereas XSLT does XML to XML transformations, JTran does JSON to JSON transformations.
Install-Package JTran
Note: These samples use the old way of creating a transformer directly (which is still allowed). Use the new TransformerBuilder class below instead.
A transform is a JSON file that contains JTran processing instructions. To transform a source JSON document you provide the source JSON and the transform:
public class JTranSample
{
public string Transform(string transform, string source)
{
var transformer = new JTran.Transformer(transform);
var context = new TransformContext { Arguments = new Dictionary<string, object>() };
return transformer.Transform(source, context);
}
}
You can input and output directly from/to streams
public class JTranSample2
{
public void Transform(string transform, Stream input, Stream output)
{
var transformer = new JTran.Transformer(transform);
var context = new TransformContext { Arguments = new Dictionary<string, object>() };
transformer.Transform(input, output, context);
}
}
You can input directly from a POCO or a list of POCOS
public class JTranSample3
{
public void TransformSchool(string transform, School input, Stream output)
{
var transformer = new JTran.Transformer(transform);
transformer.Transform(input, output);
}
public void TransformEmployees(string transform, List<Employee> input, Stream output)
{
var transformer = new JTran.Transformer(transform);
transformer.Transform(input, output);
}
}
You can output to multiple json documents, e.g. files by using the IStreamFactory. Note that your JTran must output to an array, e.g. use "[]" in a #foreach. Each object in that array is output as a separate document.
public class JTranSample4
{
public void TransformToFiles(string transform, Stream data)
{
var transformer = new JTran.Transformer(transform);
// FileStreamFactory is provided by the JTran library but you can implement your own class.
// See the MongoDBTests project for an example.
var output = new FileStreamFactory((index)=> $"c:\\documents\jtran\file_{index}.json"); // Pass in a lambda to name each file
transformer.Transform(data, output);
}
}
Return a POCO
public class JTranSample5
{
public List<Student> Transform(string transform, Stream input)
{
var transformer = new JTran.Transformer(transform);
using var output = new MemoryStream();
transformer.Transform(input, output);
return output.ToObject<List<Student>>();
}
}
The TransformerContext provides a way of extending a default transform
public class JTranSample
{
public string Transform(string transform, string source)
{
var transformer = new JTran.Transformer(transform);
var context = new TransformContext { Arguments = new Dictionary<string, object>() };
return transformer.Transform(source, context);
}
}
IDictionary<string, object>? Arguments
This is a dictionary to pass in a set of arguments. This dictionary is called at runtime and since it's an interface you could implement a custom dictionary that returns values from a secret store or configuration store, e.g. KeyVault or Microsoft.Extensions.IConfiguration
IDictionary<string, IDocumentRepository> DocumentRepositories
This is a dictionary of document repositories. These are how calls to the document() function are resolved. The repo name as the first argument in that function is the key in the dictionary you provide here.
bool AllowDeferredLoading
When the input source document is a json array (starts with "[") by default the transform will not load the json until it starts to get processed, e.g. thru a #foreach loop and will only load one item at at time. This is to allow super large json documents that would otherwise cause memory issues. However in certain cases this may cause performance issues. For instance if you jtran code is accessing items out of order, e.g. "#(@[42])". Setting this value to false will cause the entire json source to be parsed and loaded at once.
IReadOnlyDictionary<string, object> OutputArguments
This dictionary is a readonly value. It where any output variables set in the transform, e.g. "#outputvariable(Name, 'Fred')". Once the transform is complete this dictionary will be filled with output variables
Action<string, object>? OnOutputArgument
Allows a lambda expression to passed in that will be called immediately as soon as an output variable is set in the transform.
The TransformerBuilder allows you to create a JTran Transformer object by chaining functions.
var result = TransformerBuilder
.FromString(transformerSource)
.AddInclude("default", str)
.AddArguments(new MyArgumentsProvider())
.AddDocumentRepository(new MyDocumentsRepository())
.AddExtension(new MyFunctionLib())
.Build<string>()
.Transform(data);
Creating a transformer in dependency injection (.Net Core+)
public static class ServiceCollectionExtensions
{
public static void AddTransformer<T>(this IServiceCollection collection, string transformerPath)
{
collection.AddSingleton<ITransformer<T>>((p) =>
{
var blobStore = p.GetRequiredService<IBlobStore<T>>(); // This interface is for example only, it is not included in JTran
return TransformerBuilder
.FromString(blobStore.Get("transforms/" + transformerPath + ".jtran"))
.AddInclude("default", blobStore.Get("includes/defaultInclude.json"))
.AddArguments(p.GetRequiredService<IMyArgumentsProvider>())
.AddArguments(p.GetRequiredService<IMyOtherArgumentsProvider>())
.AddDocumentRepository(p.GetRequiredService<IDocumentRepository>())
.AddExtension(new MyDomainLib())
.AddExtension(new MyStringLib())
.Build<T>();
});
}
public static void SetUpMyApp<T>(this IServiceCollection collection)
{
// Create different transforms and differentiate by a class
collection.AddTransformer<Customer>("customer")
.AddTransformer<Employee>("employee")
.AddTransformer<Order>("order")
}
}
// Inject the employee transformer
public class EmployeeService(ITransformer<Employee> transformer)
{
...
}
ITransformerBuilder FromString(string transformerSource)
Creates a TransformerBuilder object from a string source. This function must be the first called in the call chain.
ITransformerBuilder FromStream(Stream transformerSource)
Creates a TransformerBuilder object from a stream source. This function must be the first called in the call chain.
ITransformerBuilder AddArguments(IReadOnlyDictionary<string, object> args)
Adds a read only dictionary as an arguments provider. Multiple providers can be added by calling this function for each one.
ITransformerBuilder AddDocumentRepository(string name, IDocumentRepository repo)
Adds a source for documents. Multiple document repositories can be added by calling this function for each one but the name must be unique for each call.
ITransformerBuilder AddExtension(object extensionObject)
Adds an object that contains custom functions for the transform. Multiple extensions can be added by calling this function for each one.
ITransformerBuilder AddInclude(string name, string includeSource)
Adds an include file. Multiple includes can be added by calling this function for each one but the name must be unique for each call including the various flavors of this function.
ITransformerBuilder AddInclude(object extensionObject, Func<string> includeSource)
Adds an include file by running the given lambda expression. Multiple includes can be added by calling this function for each one but the name must be unique for each call including the various flavors of this function.
return TransformerBuilder.FromString(blobStore.Get("transforms/" + transformerPath + ".jtran"))
.AddInclude("default", ()=>
{
... Load include
})
.AddArguments(p.GetRequiredService<IMyArgumentsProvider>())
.AddArguments(p.GetRequiredService<IMyOtherArgumentsProvider>())
.AddDocumentRepository(p.GetRequiredService<IDocumentRepository>())
.AddExtension(new MyDomainLib())
.AddExtension(new MyStringLib())
.Build<T>();
async Task<ITransformerBuilder> AddIncludeAsync(string name, Func<Task<string>> includeSource)
Adds an include file by running the given lambda expression. Multiple includes can be added by calling this function for each one but the name must be unique for each call including the various flavors of this function.
var builder = await TransformerBuilder
.FromString(transformSource)
.AddIncludeAsync("default", await ()=>
{
... Load include asynchronously
});
return builder.Build<T>();
ITransformerBuilder AllowDeferredLoading(object extensionObject)
Sets the flag to allow deferred loading. Note that deferred loading is allowed by default so you need only call this to turn it off.
ITransformerBuilder OnOutputArgument(Action<string, object> action)
Add a handler for output variables.
ITransformer Build()
Creates a transormer object from all the previous items added. Note that this must be the last function in the call chain.