NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
NuGet · #2906 most downloaded on NuGet
This package contains the core SmartFormat assemblies with core extensions built-in. SmartFormat is a lightweight text templating library written in C#. It can format various data sources into a string with a minimal, intuitive syntax similar to string.Format. It uses extensions to provide named placeholders, localization, pluralization, gender conjugation, and list and time formatting.
Last release 1 years ago
13 Sep 2025
Ships fairly regularly
a new release about every 2 months
Most releases are documented
notes for 11 of 15 stable releases
1 version withdrawn
withdrawn after publishing
127 years old
16 releases · first in 1900
Bump codecov/codecov-action from 4 to 5 by @dependabot [bot] in #476
Publish workflow branch-agnostic in #481Publish workflow branch-agnostic in #482LocalizationProvider.GetString with fallback culture in #484Parser.ParseFormat: Reduce cognitive complexity in #489Full Changelog: v3.6.0...v3.6.1
One column per quarter.
The parsing logic in Parser has been refactored by replacing stateful instance variables.
Parser:
Parser has been refactored by replacing stateful instance variables.Parser.ParseFormat(...) method is now thread-safe, ensuring safe operations in multi-threaded environments.SmartFormatter:
SmartFormatter.Format... methods are thread-safe.ThreadStatic attribute from the Smart.Default instance of SmartFormatter.SmartFormatter instances using different Smart.Extensions.Parser, Smart, and SmartFormatter classes to clarify the thread safety of methods.Also see further remarks regarding thread-safety in the Wiki
ThreadStatic Attribute for Smart.Default:
ThreadStatic attribute for the Smart.Default instance of SmartFormatter has been removed.SmartFormatter Instance with Several Threads in ParallelThe following example demonstrates how to use a single SmartFormatter instance with multiple threads in parallel. This ensures thread-safe operations and efficient resource utilization.
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SmartFormat;
public class Program
{
public static void Main()
{
// Create a single instance of SmartFormatter
var smartFormatter = Smart.CreateDefaultSmartFormat();
// Concurrent dictionary to store results
var results = new ConcurrentDictionary<long, string>();
var options = new ParallelOptions { MaxDegreeOfParallelism = 100 };
// Run parallel tasks
Parallel.For(0L, 1000, options, i =>
{
// Re-use the same SmartFormatter instance, where the Format method is thread-safe.
// The static Smart.Format can be used as well, producing the same results
results.TryAdd(i, smartFormatter.Format("{0:D3}", i));
});
// Output results
var sortedResult = results.OrderBy(r => r.Value).ToList();
foreach (var result in sortedResult)
{
Console.WriteLine(result.Value);
}
}
}SmartFormatter.Format(...) methods thread-safe in #473Full Changelog: v3.5.3...v3.6.0
Add support for specifying separator between units when using TimeFormatter by @hakksor in #459
TimeFormatter allows for nested formats:
var ci = CultureInfo.GetCultureInfo("en");
// Using standard:
_ = Smart.Format(ci, "{1:time:", new TimeSpan(1,1,1,1,1));
// Output: "1 day 1 hour 1 minute 1 second"
// Using ListFormatter:
_ = Smart.Format(ci, "{1:time: {:list:|, | and }}", new TimeSpan(1,1,1,1,1));
// Output: "1 day, 1 hour, 1 minute and 1 second"Full Changelog: v3.5.2...v3.5.3
Enhancement: Enable chaining from StringBuilder.AppendSmart() and StringBuilder.AppendLineSmart() by @Klikini in #452
StringBuilder.AppendSmart() and StringBuilder.AppendLineSmart() by @Klikini in #452Full Changelog: v3.5.1...v3.5.2
Microsoft Security Advisory CVE-2024-43485 in #442
Full Changelog: v3.5.0...v3.5.1
Fixed a vulnerability in .NET when calling the JsonSerializer.DeserializeAsyncEnumerable method against an untrusted input using System.Text.Json, whi…
IFormattingExtensionsToggle interface to allow skipping formatting by IFormatter extensions.ISource extensions that receive it with the ISelectorInfo parameter.IFormattingExtensionsToggle.DisableFormattingExtensions to true, formatting can be skipped. This can be useful when the ISource found a value in ISource.TryEvaluateSelector where default formatting cannot reasonably be done.System.Text.Json to v8.0.4ISpanFormattable for DefaultFormatterISpanFormattable is 5% faster than IFormattable, with 24% less allocations
// Performance test case
Smart.FormatInto(output, null, _placeholder0005Format, 1234567.890123f, 1234567.890123f, 1234567.890123f, 1234567.890123f, 1234567.890123f); BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3737/23H2/2023Update/SunValley3)
13th Gen Intel Core i7-13700K, 1 CPU, 24 logical and 16 physical cores
.NET SDK 8.0.302
[Host] : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2
Job=.NET 8.0 Runtime=.NET 8.0
| Method | N | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------------------------- |------ |--------------:|------------:|------------:|------:|--------:|-----------:|-------------:|------------:|
| ISpanFormattable | 100 | 74.502 us | 0.3693 us | 0.3273 us | 8.52 | 0.05 | 4.0283 | 62.5 KB | 2.76 |
| IFormattable | 100 | 77.927 us | 0.5760 us | 0.5388 us | 8.91 | 0.07 | 5.2490 | 82.0 KB | 3.62 |
ReflectionSource.TypeCache static for better performance.static cache has undesired effects on your code logic, consider to disable the cache (ReflectionSource.IsTypeCacheEnabled = false).ReflectionSource.TypeCache and remove the oldest item first.DictionarySource now use case-sensitivity settingIReadOnlyDictionary now has instance scopeISource and IFormatter extensions into internal class RegistryEvaluatorSmartFormatter remain unchanged and are not yet marked as obsoleteArrayPool<char> and returns it when disposedZCharArray contains most frequently used methods for writing data into the underlying bufferFormattingInfo methods (see below) for low memory allocationAdded methods useful in custom IFormatters:
Placeholder and applies its FormatPlaceholderPlaceholder and Format objectsFormattingInfo uses to evaluate Placeholder and Format objectsSmartFormatter that are now moved to EvaluatorISource and IFormatter extensions that have been moved from SmartFormatterFormat.HasNested property that checks Items for existing PlaceholderSmartFormat.Pooling classes (#401)StringBuilder exceeding the default capacity to StringBuilderPool would throw an exception.Full Changelog: v3.4.0...v3.5.0
Switching to a different patch version of a target framework is generally not considered a breaking change. This is referred to as an in-place update…
SmartFormat has transitioned to using .NET Framework 4.6.2 (net462) as its target framework, replacing the now unsupported .NET Framework 4.6.1 (net461). The support for net461 ended in 2022, while net462 will continue to receive support until January 2027.
Switching to a different patch version of a target framework is generally not considered a breaking change. This is referred to as an in-place update by Microsoft. In the case of SmartFormat, we have not encountered any runtime issues with the existing API after this transition.
The same holds true for the new netstandard2.0 reference to ZString.
SmartFormat has expanded its compatibility by adding .NET 6.0 (net60) and .NET 8.0 (net80) as additional target frameworks. You can find details about their end of support here.
We have removed the SmartFormat.ZString assembly and replaced it with a reference to the ZString package. This change does not affect the API.
Thanks to @thompson-tomo for his first contribution with #377
Full Changelog: v3.3.2...v3.4.0
If there are namespace collisions with Cysharp.Text using v3.3.1 please update to v3.2.2
Full Changelog: v3.3.1...v3.3.2
PluralRule for DualFromZeroToTwo: Now a value of 2 is covered and will not throw. Frend is one of the affected languages. Closes #369 in #370
PluralRule for DualFromZeroToTwo: Now a value of 2 is covered and will not throw. Frend is one of the affected languages. Closes #369 in #370
Dictionary<string, PluralRuleDelegate> PluralRule.IsoLangToDelegate holds delegates with the pluralization rule per language. Changing a value of this dictionary will change the pluralization rules globally. This is not recommended, but possible. After a change calling PluralRules.RestoreDefault() will restore the default rules.
Full Changelog: v3.3.0...v3.3.1
Add support for nested formats in LocalizationFormatter by @zacateras in #350 . This is useful, if the string to localize contains a SmartFormat place
LocalizationFormatter by @zacateras in #350. This is useful, if the string to localize contains a SmartFormat placeholder instead of a pure text. Example: If the format is "{:L:{ProductType}}", the ProductType placeholder will be replaced with the variable content "pen". "pen" will in turn be localiced to "bic" for the FR locale.DictionarySource has an option to evaluate IReadOnlyDictionary<TKey,TValue> sources by @axunonb in #353. To enable, set DictionarySource.IsIReadOnlyDictionarySupported to true (default is false). This is for types that only implement IReadOnlyDictionary<TKey,TValue>, but not IDictionary.Full Changelog: v3.2.2...v3.3.0
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
> You'll find a detailed description for all changes including code samples in the [Wiki](https://github.com/axuno/SmartFormat/wiki).
You'll find a detailed description for all changes including code samples in the Wiki.
After implementing a zero allocation ValueStringBuilder (#193, #228) and Object Pools (#229) for all classes which are frequently instantiated:
Sample of BenchmarkDotNet results under NetStandard2.1:
| Method | N | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
|--------------- |------ |---------:|--------:|--------:|-----------:|----------:|------:|----------:|
SmartFormat v2.7.2 (only SingleThread)
| Format | 10000 | 223.9 ms | 1.48 ms | 1.38 ms | 21333.3333 | - | - | 172 MB |
SmartFormat v3.0.0
| SingleThread | 10000 | 108.2 ms | 0.52 ms | 0.49 ms | 3200.0000 | - | - | 26 MB |
| ThreadSafe | 10000 | 128.0 ms | 1.29 ms | 1.21 ms | 6000.0000 | - | - | 48 MB |
The main point about string.Format compatibility is, how curly braces and colons are processed in the format string.
In most cases string.Format compatibility does not bring any advantages.
With SmartSettings.StringFormatCompatibility = true SmartFormat is fully compatible. The downside, however, ist that custom formatter extensions cannot be parsed and used with this setting.
With SmartSettings.StringFormatCompatibility = false (default), all features of SmartFormat are available.
Reasoning: The distinction was necessary because of syntax conflicts between SmartFormat extensions and string.Format. It brings a more concise and clear set of formatting rules and full string.Format compatibility even in "edge cases".
C# like nullable notation allows to display Nullable<T> types safely (#176).
The SmartFormat notation is "{SomeNullable?.Property}". If SomeNullable is null, the expression is evaluated as string.Empty. "{SomeNullable.Property}" would throw a FormattingException.
Object Pools, Reflection and other caches can operate in thread-safe mode.
(SmartFormatters and Parsers still require one instance per thread.)
This was a limitation in v2. In v3, the Parser can parse any character as part of formatter options. This means e.g. no limitations for RegEx expressions used in IsMatchFormatter. Note: Special characters like (){}:\ must be escaped with \.
Literals may contain any Unicode characters (#166). Add unicode escape characters like "\u1234". Thanks to @karljj1.
Global support for Alignment (#174): In v2, Alignment of output values was limited to the DefaultFormatter. It's about the equivalent to e.g. string.Format("{0,10}"). Alignment is now supported by all IFormatters.
Full control about output characters, including whitespace.
Introduced bool ParserSettings.ParseInputAsHtml.
The default is false.
If true, theParser will parse all content inside <script> and <style> tags as LiteralText. All other places may still contain Placeholders.
This is because <script> and <style> tags may contain curly or square braces, that interfere with the SmartFormat {Placeholder}.
The character to split options and formats can be changed. This allows having the default split character | as part of the output string.
Affects ChooseFormatter, ConditionalFormatter, IsMatchFormatter, ListFormatter, PluralLocalizationFormatter, SubStringFormatter.
ReflectionSourceAdded a type cache which increases speed by factor 4. Thanks to @karljj1. (#155)
DictionarySourceSpeed increased by 10% with less GC pressure (#189).
IsMatchFormatterThe IsMatchFormatter is a formatter with evaluation of regular expressions. It allows to control its output depending on a RegEx match.
New: The formatter can output matching group values of a RegEx (#245).
PluralLocalizationFormatter (#209)DefaultTwoLetterISOLanguageName is removed.CultureInfo.InvariantCulture maps to CultureInfo.GetCultureInfo("en") (#243).Culture is now determined in this sequence (same as with LocalizationFormatter):<br/>
a) Get the culture from the FormattingInfo.FormatterOptions.<br/>
b) Get the culture from the IFormatProvider argument (which may be a CultureInfo) to SmartFormatter.Format(IFormatProvider, string, object?[])<br/>
c) The CultureInfo.CurrentUICulture<br/>
TimeFormatter (#220, #221, #234)DefaultTwoLetterISOLanguageName is removed.Culture is now determined in this sequence (same as with LocalizationFormatter):<br/>
a) Get the culture from the FormattingInfo.FormatterOptions.<br/>
b) Get the culture from the IFormatProvider argument (which may be a CultureInfo) to SmartFormatter.Format(IFormatProvider, string, object?[])<br/>
c) The CultureInfo.CurrentUICulture<br/>
Extended CommonLanguagesTimeTextInfo, which now includes French, Spanish, Portuguese, Italian and German as new languages besides English out-of-the-box.
This notation - using formats as formatter options - was allowed in SmartFormat v2.x, but is now depreciated. It is still detected and working, as long as the format part is left empty.
var formatDepreciated = "{0:time(abbr hours noless)}";
This format string is recommended for SmartFormat v3 and later. It allows for including the language as an option to the TimeFormatter:
// Without language option:
var formatRecommended = "{0:time:abbr hours noless:}";
// With language option:
var formatRecommended = "{0:time(en):abbr hours noless:}";
SubStringFormatterThe formatter now accecpts a format argument with a nested Placeholder that lets you format the result of the sub-string operation (#258).
Example: Convert the sub-string to lower-case:
Smart.Format("{0:substr(0,2):{ToLower}}", "ABC");
ChooseFormatter #253Modified ChooseFormatter case-sensitivity for option strings. This modification is compatible with v2:
bool and null as string: always case-insensitiveSmartSettings.CaseSensitivity unless overridden with ChooseFormatter.CaseSensitivityStringSourceThe StringSource takes over a part of the functionality, which has been implemented in ReflectionSource in v2. Compared to reflection with caching, speed is 20% better at 25% less memory allocation. (#178, #216)
KeyValuePairSource (#244)The KeyValuePairSource is a simple, cheap and performant way to create named placeholders.
Separation of JsonSource into 2 ISource extensions (#177, #201):
NewtonSoftJsonSourceSystemTextJsonSourcePersistentVariableSource and GlobalVariableSourceBoth provide global variables that are stored in VariablesGroup containers. These variables are not passed in as arguments when formatting a string. Instead, they are taken from one of these two registered (global) ISources. (#233)
Credits to Needle and their PersistentVariablesSource extension to SmartFormat.
NullFormatterIn the context of Nullable Notation (see below), the NullFormatter has been added. It outputs a custom string literal, if the variable is null, else another literal (default is string.Empty) or a nested Placeholder. (#176, #199)
LocalizationFormatterAdded LocalizationFormatter to localize literals and placeholders (#207).
Added ILocalizationProvider and a standard implemention as LocalizationProvider, which handles resx resource files. A fallback culture can be set. LocalizationProvider can search an unlimited number of defined resoures.
IFormatters have one single, unique name (#185).
In v2, IFormatters could have an unlimited number of names.
IInitializer InterfaceAny (custom) ISource and IFormatter can implement IInitializer. Then, the SmartFormatter will call Initialize(SmartFormatter smartFormatter) of the extension, before adding it to the extension list (#180).
SmartFormatter.Format(...)Added support for IList<object> parameters to the SmartFormatter (thanks to @karljj1) (#154)
SmartObjectsRemoved obsolete SmartObjects (which have been replaced by ValueTuple) (092b7b1)
Introduced experimental bool ParserSettings.ParseInputAsHtml.
The default is false.
If true, theParser will parse all content inside <script> and <style> tags as LiteralText. This is because <script> and <style> tags may contain curly or square braces, that interfere with the SmartFormat {Placeholder}.
All other places may still contain Placeholders. (#203)
SmartFormat.NET
This is a package which references all packages below.
SmartFormat
SmartFormat is the core package. It comes with the most frequently used extensions built-in.
SmartFormat.Extensions.System.Text.Json
This package is a SmartFormat extension for formatting System.Text.Json types as a source.
SmartFormat.Extensions.Newtonsoft.Json
This package is a SmartFormat extension for formatting Newtonsoft.Json types as a source.
SmartFormat.Extensions.Xml
This package is a SmartFormat extension for reading and formatting System.Xml.Linq.XElements.
SmartFormat.Extensions.Time
This package is a SmartFormat extension for formatting System.DateTime, System.DateTimeOffset and System.TimeSpan types.
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →