SkillAgentSearch skills...

StringTokenFormatter

Provides string extension methods for replacing tokens within strings (using the format '{name}') with their specified lookup value.

Install / Use

/learn @andywilsonuk/StringTokenFormatter
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

StringTokenFormatter v9.0

Provides token replacement for template strings which cannot be interpolated at compile time such as those retrieved from data stores (file system, database, API, config files etc) using a variety of token to value mappers.

It isn’t a replacement for String Interpolation where the string is known at compile time and instead builds upon the idea of string.Format where a template string is passed along with a series of replacement values. This library offers additional features beyond what either of the out-of-the-box implementations provide.

Supported platforms: .NET 5 and later, .NET Standard 2.0

Available on nuget.org at https://www.nuget.org/packages/StringTokenFormatter.

using StringTokenFormatter;

string interpolatedString = "Hello {FirstName} {LastName}";
var client = new {
    FirstName = "John",
    LastName = "Smith",
};
string message = interpolatedString.FormatFromObject(client);

See the v9 migration page for details on breaking changes and how to upgrade from version 8 to version 9.

Overview

As well as string extensions, there are equivalent Uri extensions, the preferred method is to use the dependency injection friendly Resolver which makes sharing custom settings and working with complex templates easier.

string templateString = "Answer is {percent,10:P}";
var resolver = new InterpolatedStringResolver(StringTokenFormatterSettings.Default);

string actual = resolver.FromTuples(templateString, ("percent", 1.2));

Assert.Equal("Answer is    120.00%", actual);

Tokens with formatting and alignment can be specified in the same way as string.Format (.net docs). Alternative token syntax can be selected in the settings.

Grouping tokens with prefixes, multiple containers and other complex token resolution setups are supported through the CompositeTokenValueContainer, see Building composite containers for the helper class.

Conditional blocks of text, loops and simple value mapping can be controlled through commands.

As well supporting formatting through IFormatProvider, strongly-type FormatterDefinitions functions can be configured to match type, token name or format string.

Value Containers can return primitives (string, int etc) as well as Func or Lazy values which will be resolved before formatting using one of the Value Converters. Additional converters can be included to perform custom conversion logic after token matching but before formatting.

A more complete example:

static string OrderIdFormatter(int id, string _format) => $"#{id:000000}";
static string GuidFormatter(Guid guid, string format) =>
    format == "Initial" ? guid.ToString("D").Split('-')[0].ToUpper() : guid.ToString();

var settings = StringTokenFormatterSettings.Default with
{
    FormatProvider = CultureInfo.GetCultureInfo("en-US"),
    FormatterDefinitions = new[] {
        FormatterDefinition.ForTokenName<int>("Order.Id", OrderIdFormatter),
        FormatterDefinition.ForType<Guid>(GuidFormatter),
    },
};
var resolver = new InterpolatedStringResolver(settings);
string templateString = new StringBuilder()
    .AppendLine("Hi {Customer.Name},")
    .AppendLine("Thank you for {:map,Customer.IsFirstOrder:true=your first order,false=your order}.")
    .AppendLine("Order details")
    .AppendLine("- Id: {Order.Id}")
    .AppendLine("- Payment method: {:map,Order.PaymentMethod:DebitCard=Debit card,CreditCard=Credit card}")
    .AppendLine("- Delivery option: {Order.Delivery}")
    .AppendLine("{:if,Order.HasDeliveryComment}- Comment for delivery driver: {Order.DeliveryComment}{:ifend}")
    .AppendLine("Items")
    .Append("{:loop,OrderLines}")
    .AppendLine("- {OrderLines.Product} @ {OrderLines.Price:C}")
    .Append("{:loopend}")
    .AppendLine("Total: {OrderTotal:C}")
    .Append("Ref: {MessageId:Initial}")
    .ToString();
var interpolatedString = resolver.Interpolate(templateString);

var customer = new
{
    Name = "Jane Strong",
    IsFirstOrder = true,
};
var order = new Dictionary<string, object>()
{
    ["Id"] = 8321,
    ["PaymentMethod"] = "CreditCard",
    ["Delivery"] = "Next day",
    ["DeliveryComment"] = "Please leave if no one in",
};
var orderLines = new[]
{
    new OrderLine(product: "T-shirt", price: 25.5),
    new OrderLine(product: "Coat", price: 40.0),
    new OrderLine(product: "Socks", price: 14.0),
};
var combinedContainer = resolver.Builder()
    .AddPrefixedObject("Customer", customer)
    .AddPrefixedKeyValues("Order", order)
    .AddPrefixedSingle("Order", "HasDeliveryComment", order["DeliveryComment"] is not null)
    .AddSequence("OrderLines", orderLines)
    .AddSingle("OrderTotal", orderLines.Sum(x => x.Price))
    .AddSingle("MessageId", new Lazy<object>(() => Guid.Parse("73054fad-ba31-4cc2-a1c1-ac534adc9b45")))
    .CombinedResult();

string actual = resolver.FromContainer(interpolatedString, combinedContainer);

string expected = """
Hi Jane Strong,
Thank you for your first order.
Order details
- Id: #008321
- Payment method: Credit card
- Delivery option: Next day
- Comment for delivery driver: Please leave if no one in
Items
- T-shirt @ $25.50
- Coat @ $40.00
- Socks @ $14.00
Total: $79.50
Ref: 73054FAD
""";

Assert.Equal(expected, actual);

record OrderLine(string product, double price);

More examples.

Table of Contents

Interpolated String Resolver

Beyond simple use cases, things can get a little complicated and this is where the InterpolatedStringResolver steps in.

Within an application there’s likely to be only one or a small number of defined custom settings; these settings are passed into the constructor of the Resolver which then provides methods for converting template strings into resolved strings. The instance of the resolver can be stored in a dependency injection container for ease of use.

  • Methods FromSingle, FromPairs, FromTuples, FromObject, FromFunc and FromContainer take in a template string or InterpolatedString as well as relevant mapping values
  • The Builder method creates a new instance of the TokenValueContainerBuilder class which is used to construct complex value containers
  • Template strings can be converted into InterpolatedStrings created once and stored ahead of time; the Interpolate method parses the template string ready for resolving using token values

The resolver contains the standard token resolving methods and is the preferred mechanism over changing the global settings and using the string extension methods.

How token resolution works

To resolve the tokens within a template string, there is a two-stage process, first parsing, then expanding.

Parsing

The InterpolatedStringParser turns a template string into an InterpolatedString defined as a list of strongly-typed InterpolatedStringSegments.

Generating the InterpolatedString can be stored and used multiple times with the InterpolatedStringExpander.

flowchart LR
    Settings[/settings/]
    TemplateString[/template string/]
    Parser[InterpolatedStringParser]
    InterpolatedStringSegments[/segments/]
    InterpolatedString[/InterpolatedString/]
    TemplateString --> Parser
    Settings --> Parser
    Parser --> InterpolatedStringSegments
    InterpolatedStringSegments --> InterpolatedString

Expanding

The InterpolatedStringExpander take the InterpolatedString and processes it using the values in the ValueContainer.

  1. The passed ITokenValueContainer provides the value based on the token name
  2. A value conversion is then attempted based on the collection of TokenValueConverters in the settings
  3. Value is then formatted using a Formatter Defintion or FormatProvider from the settings

Block Commands are processed by the InterpolatedStringExpander and follow the flow of steps 2.1 and 2.2 for obtaining their relevant values from tokens.

flowchart LR
    InterpolatedString[/InterpolatedString/]
    ValueContainer[/ValueContainer/]
    Expander[InterpolatedStringExpander]
    Segment[/segment/]
    Command[Command]
    ValueConverter[Value Converter]
    FormatterDefinition[Formatter Definition]
    FormatProvider[Format Provider]
    ResultantStringSegment[/resultant string/]
    ResultantString[/combined string/]
    subgraph SegmentProcessing[Segment Processing]
        direction TB
        Segment --> Command
        Command --> ValueConverter
        ValueConverter --> FormatterDefinition
        ValueConverter --> FormatProvider
        FormatterDefinition --> ResultantStringSegment
        FormatProvider --> ResultantStringSegment
    end
    InterpolatedString --> Expander
    ValueContainer --> Expander
    Expander --> SegmentProcessing
    SegmentProcessing --> 

Related Skills

View on GitHub
GitHub Stars39
CategoryProduct
Updated5mo ago
Forks3

Languages

C#

Security Score

87/100

Audited on Oct 26, 2025

No findings