StringTokenFormatter
Provides string extension methods for replacing tokens within strings (using the format '{name}') with their specified lookup value.
Install / Use
/learn @andywilsonuk/StringTokenFormatterREADME
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
- Overview
- Interpolated String Resolver
- How token resolution works
- Value Containers
- Formatter Definitions
- Commands
- Building composite token value containers
- Settings
- Exceptions
- Custom Value Containers
- Value Converters
- Performance tuning
- Thread safety
- Frozen Dictionary support in .NET 8
- Async loading of token values
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,FromFuncandFromContainertake in a templatestringorInterpolatedStringas well as relevant mapping values - The
Buildermethod creates a new instance of theTokenValueContainerBuilderclass which is used to construct complex value containers - Template strings can be converted into
InterpolatedStrings created once and stored ahead of time; theInterpolatemethod 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.
- The passed
ITokenValueContainerprovides the value based on the token name - A value conversion is then attempted based on the collection of
TokenValueConverters in the settings - Value is then formatted using a Formatter Defintion or
FormatProviderfrom 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
product-manager-skills
52PM skill for Claude Code, Codex, Cursor, and Windsurf: diagnose SaaS metrics, critique PRDs, plan roadmaps, run discovery, and coach PM career transitions.
snap-vis-manager
The planning agent for the snap-vis project. Coordinates other specialized agents and manages the overall project roadmap.
devplan-mcp-server
3MCP server for generating development plans, project roadmaps, and task breakdowns for Claude Code. Turn project ideas into paint-by-numbers implementation plans.
