Skip to content

Placeholders and Nesting

axunonb edited this page Jul 26, 2025 · 13 revisions

Indexed placeholders

string.Format references all args by index:

string.Format("{0} {1}", person.FirstName, person.LastName)

SmartFormat can do very the same:

Smart.Format("{0} {1}", person.FirstName, person.LastName)

Named placeholders

SmartFormat takes indexed placeholders a step further, and lets you use named placeholders instead:

Smart.Format("{FirstName} {LastName}", person)

In fact, SmartFormat even supports several kinds of expressions (parameterless methods) to be used in placeholders:

Smart.Format("{FirstName.ToUpper} {LastName.ToLower}", person)

where ToUpper is a parameterless method.

The Special {} Placeholder

The {} placeholder is a special placeholder that refers to the current scope. It is often used with conditionals, plurals, and lists, but can also be used to access the current scope directly. Read more about this in the Nesting section.

var data = new { City = new { Region = "river side"}};
// Instead of writing:
var result = Smart.Format("The city region is {City.Region}.", data);
// You can also use a nested Placeholder
// which gets the scope of the parent Placeholder:
result = Smart.Format("{City.Region:The city region is {}.}", data);

Nesting Placeholders

var person = new
{
    Person = new
    {
        FirstName = "John",
        LastName = "Long",
        Address = new { City = "London", Street = "Main St" }
    }
};

In SmartFormat, you can use nesting to avoid repetition, such as:

Smart.Format("City: {Person:{Address:{City}}, Name: {FirstName}}", person);
// Outputs: "City: London, Name: John"

Nesting is often used with conditionals, plurals, and lists:

var data = new 
{
    People = new List<object> 
    {
        new {Name = "Name 1", Age = 20}
    }
};
Smart.Format("There {People.Count:is a person.|are {} people.}", data);
// Outputs: "There is a person."

// After adding one more item to the People list:
// Outputs: "There are 2 people."

In the example above you'll notice the empty placeholder {}. It uses the scope, which is People.Count, and outputs its value.

Nested Scope

Let's take a closer look at the Nested Placeholders example:

When using nested placeholders, it is necessary to understand the scope that SmartFormat will use. A nested placeholder always starts off with the scope of the item that contains it.

The root scope for the template above is Person, meaning that {Person.Address} is equivalent to C# person.Person.Address.

Within the nested area, however, the "scope" has changed to Person.Address, so nested expressions like {Street} are evaluated against Person.Address.

To illustrate this, the following are all equivalent, while the last one is the shortest form:

  • {Person.Name} {Person.Address.City} {Person.Address.Street}
  • {Person.Name} {Person.Address:{City} {Street}}
  • {Person:{Name} {Address:{City} {Street}}}

Within any nested scope, you still have access to the outer scopes. For example:
{Person.Address:{Person.Name} {City} {Street}}
Here, {Person.Name}, which is in the root scope, is still accessible from within the nested Person.Address scope.

Nested Scope With Parent Scope Resolution

Note, that in the following sameple the property with name Threshold is defined in the Override scope, and also in the Settings scope.

{
var settings = new
{
    Settings = new
    {
        Maximum = 100,
        Threshold = 1,
        Override = new
        {
            Threshold = 99
        }
    }
};

var result = Smart.Format("OverrideThreshold = {Settings:{Override:{Threshold}}}", settings);
// result: "OverrideThreshold = 99"

Now, what happens if the Threshold property is not defined in the Override scope, but only in the Settings scope?

var settings = new
{
    Settings = new
    {
        Maximum = 100,
        Threshold = 1,
        Override = new
        {
            // Property does not exist
            // Threshold = 99
        }
    }
};

// The first scope is Settings, the second is Override
var result = Smart.Format("Threshold = {Settings:{Override:{Threshold}}}", settings);
// result: "Threshold = 1"

In this case, SmartFormat will look for the Threshold property in the Override scope first. Since it does not exist there, it will fallback to the parents' scope, which is Settings, and find the value 1.

This fallback mechanism is useful when you want to override properties in a nested scope but still have a default value available in the parent scope.

Caution

The parent scope resolution can be a useful feature, but it can also lead to unexpected results if not used carefully. The fallback only occurs for undefined properties. It does not apply to null property values.

// Use Setttings.Threshold if Override.Threshold is null:
var result = Smart.Format("Threshold = {Settings:{Override:{Threshold:isnull:{Settings.Threshold}|{}}}}", settings);

Caution

The fallback mechanism only works for nested placeholders and Scoped Notation. It does not apply to Dot Notation.

Clone this wiki locally