Map Rendering Guide

View Source

Master advanced data transformations with map directives.

Overview

The render_map function enables powerful data transformations within nested map structures using special directives. Directives allow you to iterate, filter, merge, and conditionally render maps—similar to a data transformation pipeline.

When to Use render_map

Use render_map when you need to:

  • Transform nested data structures
  • Build complex outputs programmatically
  • Filter and map collections
  • Merge multiple data sources
  • Apply conditional logic to structured data

Use regular render when you need to:

  • Generate text/HTML output
  • Render simple templates
  • Control flow in text documents

Basic Usage

input = %{
  "greeting" => "Hello {{ name }}!"
}

context = %{"name" => "World"}

{:ok, result} = Mau.render_map(input, context)
# result: %{"greeting" => "Hello World!"}

Directives Overview

All directives use keys starting with #:

DirectivePurposeSyntax
#mapIterate over collections"#map" => [collection, template]
#filterFilter collections"#filter" => [collection, condition]
#mergeCombine maps"#merge" => [map1, map2, ...]
#ifConditional rendering"#if" => [condition, true_template, false_template]
#pickExtract keys from map"#pick" => [map, keys]
#pipeThread data through transformations"#pipe" => [initial, directives]

#map Directive

Iterate over a collection and apply a template to each item.

Syntax

%{
  "key" => %{
    "#map" => [collection_template, item_template]
  }
}

Basic Example

input = %{
  "users" => %{
    "#map" => [
      "{{$users}}",
      %{"name" => "{{$loop.item.name}}"}
    ]
  }
}

context = %{
  "$users" => [
    %{"name" => "Alice"},
    %{"name" => "Bob"}
  ]
}

{:ok, result} = Mau.render_map(input, context)
# result: %{"users" => [%{"name" => "Alice"}, %{"name" => "Bob"}]}

Access Loop Variables

Available in the item template:

%{
  "#map" => [
    "{{$items}}",
    %{
      "index" => "{{$loop.index}}",
      "item" => "{{$loop.item}}",
      "is_first" => "{{$loop.first}}"
    }
  ]
}

Loop variables:

  • $loop.item - Current item
  • $loop.index - Position (0-based)
  • $loop.parentloop - Parent loop (in nested maps)

Nested Maps

input = %{
  "departments" => %{
    "#map" => [
      "{{$departments}}",
      %{
        "name" => "{{$loop.item.name}}",
        "employees" => %{
          "#map" => [
            "{{$loop.item.employees}}",
            %{"name" => "{{$loop.item.name}}"}
          ]
        }
      }
    ]
  }
}

#filter Directive

Filter items based on a condition.

Syntax

%{
  "key" => %{
    "#filter" => [collection_template, condition_template]
  }
}

Example

input = %{
  "active_users" => %{
    "#filter" => [
      "{{$users}}",
      "{{$loop.item.active}}"
    ]
  }
}

context = %{
  "$users" => [
    %{"name" => "Alice", "active" => true},
    %{"name" => "Bob", "active" => false},
    %{"name" => "Charlie", "active" => true}
  ]
}

{:ok, result} = Mau.render_map(input, context)
# result: %{"active_users" => [
#   %{"name" => "Alice", "active" => true},
#   %{"name" => "Charlie", "active" => true}
# ]}

Complex Conditions

"#filter" => [
  "{{$users}}",
  "{{$loop.item.age > 18 and $loop.item.verified}}"
]

#merge Directive

Combine multiple maps together.

Syntax

%{
  "key" => %{
    "#merge" => [map1_template, map2_template, ...]
  }
}

Example

input = %{
  "user_profile" => %{
    "#merge" => [
      "{{$user}}",
      %{"role" => "{{$role}}", "verified" => true}
    ]
  }
}

context = %{
  "$user" => %{"name" => "Alice", "email" => "alice@example.com"},
  "$role" => "admin"
}

{:ok, result} = Mau.render_map(input, context)
# result: %{"user_profile" => %{
#   "name" => "Alice",
#   "email" => "alice@example.com",
#   "role" => "admin",
#   "verified" => true
# }}

#if Directive

Conditionally render templates.

Syntax

# With else
%{
  "key" => %{
    "#if" => [condition, true_template, false_template]
  }
}

# Without else
%{
  "key" => %{
    "#if" => [condition, true_template]
  }
}

Example

input = %{
  "status" => %{
    "#if" => [
      "{{$user.premium}}",
      %{"level" => "premium", "price" => "$9.99"},
      %{"level" => "free", "price" => "free"}
    ]
  }
}

context = %{
  "$user" => %{"name" => "Alice", "premium" => true}
}

{:ok, result} = Mau.render_map(input, context)
# result: %{"status" => %{"level" => "premium", "price" => "$9.99"}}

#pick Directive

Extract specific keys from a map.

Syntax

%{
  "key" => %{
    "#pick" => [map_template, keys]
  }
}

Example

input = %{
  "public_profile" => %{
    "#pick" => [
      "{{$user}}",
      ["name", "email", "avatar"]
    ]
  }
}

context = %{
  "$user" => %{
    "name" => "Alice",
    "email" => "alice@example.com",
    "avatar" => "alice.jpg",
    "password_hash" => "secret"  # Won't be included
  }
}

{:ok, result} = Mau.render_map(input, context)
# result: %{"public_profile" => %{
#   "name" => "Alice",
#   "email" => "alice@example.com",
#   "avatar" => "alice.jpg"
# }}

#pipe Directive

Thread data through a series of transformations.

Syntax

%{
  "key" => %{
    "#pipe" => [initial_value, [directive1, directive2, ...]]
  }
}

How It Works

The piped value is automatically injected as the first argument to each directive. Access it via $self context variable.

Basic Example

input = %{
  "result" => %{
    "#pipe" => [
      "{{$items}}",
      [
        %{"#filter" => "{{$loop.item.price > 100}}"},
        %{"#map" => %{"name" => "{{$loop.item.name}}"}}
      ]
    ]
  }
}

context = %{
  "$items" => [
    %{"name" => "Item A", "price" => 50},
    %{"name" => "Item B", "price" => 150},
    %{"name" => "Item C", "price" => 200}
  ]
}

{:ok, result} = Mau.render_map(input, context)
# result: %{"result" => [
#   %{"name" => "Item B"},
#   %{"name" => "Item C"}
# ]}

Multi-Stage Pipeline

%{
  "#pipe" => [
    "{{$data}}",
    [
      %{"#filter" => "{{$loop.item.active}}"},
      %{"#map" => %{"username" => "{{$loop.item.name}}"}},
      %{"#filter" => "{{$loop.item.username != 'admin'}}"}
    ]
  ]
}

Advanced Patterns

Combining Directives

Use multiple directives in nested structures:

input = %{
  "report" => %{
    "#merge" => [
      %{"title" => "User Report"},
      %{
        "users" => %{
          "#map" => [
            "{{$users}}",
            %{
              "name" => "{{$loop.item.name}}",
              "profile" => %{
                "#if" => [
                  "{{$loop.item.premium}}",
                  %{"tier" => "premium"},
                  %{"tier" => "free"}
                ]
              }
            }
          ]
        }
      }
    ]
  }
}

Data Transformation Pipeline

Create data flows similar to Elixir pipes:

input = %{
  "processed" => %{
    "#pipe" => [
      "{{$raw_data}}",
      [
        %{"#filter" => "{{not nil}}"},           # Remove nils
        %{"#map" => %{                            # Transform
          "value" => "{{$loop.item | upper_case}}"
        }},
        %{"#filter" => "{{$loop.item.value}}"}   # Filter empty
      ]
    ]
  }
}

Context Variables

Special variables available in templates:

{{$users}}              # From context
{{$loop.item}}          # Current item in map/filter
{{$loop.index}}         # Current index
{{$self}}               # Piped value (in pipe directive)

Regular template expressions also work:

"count" => "{{items | length}}"
"active" => "{{user.verified and user.active}}"

Complete Example: API Response Transformation

Transform an API response into a structured format:

input = %{
  "data" => %{
    "#merge" => [
      %{
        "timestamp" => "{{now}}",
        "source" => "api"
      },
      %{
        "users" => %{
          "#pipe" => [
            "{{$response.users}}",
            [
              %{"#filter" => "{{$loop.item.active}}"},
              %{"#map" => %{
                "id" => "{{$loop.item.id}}",
                "name" => "{{$loop.item.full_name}}",
                "contact" => %{
                  "#merge" => [
                    %{"email" => "{{$loop.item.email}}"},
                    %{"phone" => "{{$loop.item.phone}}"}
                  ]
                }
              }}
            ]
          ]
        }
      }
    ]
  }
}

context = %{
  "now" => "2024-01-15T10:30:00Z",
  "$response" => %{
    "users" => [
      %{
        "id" => 1,
        "full_name" => "Alice Johnson",
        "email" => "alice@example.com",
        "phone" => "555-0001",
        "active" => true
      },
      %{
        "id" => 2,
        "full_name" => "Bob Smith",
        "email" => "bob@example.com",
        "phone" => "555-0002",
        "active" => false
      }
    ]
  }
}

{:ok, result} = Mau.render_map(input, context)

Result:

%{
  "data" => %{
    "timestamp" => "2024-01-15T10:30:00Z",
    "source" => "api",
    "users" => [
      %{
        "id" => 1,
        "name" => "Alice Johnson",
        "contact" => %{
          "email" => "alice@example.com",
          "phone" => "555-0001"
        }
      }
    ]
  }
}

Comparing render vs render_map

Featurerenderrender_map
Output TypeStringMap/Structure
Use CaseText/HTML outputData transformation
Syntax{{ }} and {% %}Directive maps
Iteration{% for %}#map directive
Filtering| filter#filter directive
Conditionals{% if %}#if directive

Best Practices

  1. Validate Input Data

Always check if required data exists before transforming.

  1. Use Meaningful Directive Keys
%{
  "active_users" => %{    # Clear intent
    "#filter" => [...]
  }
}
  1. Break Down Complex Transformations

Use intermediate steps for clarity:

%{
  "filtered" => %{
    "#filter" => [...]
  },
  "transformed" => %{
    "#map" => ["{{filtered}}", ...]
  }
}
  1. Comment Complex Structures
# Step 1: Filter active users
# Step 2: Map to names only
# Step 3: Sort results
%{"#pipe" => [...]}

Troubleshooting

Directive Not Applied

Problem: Directive key not recognized

Solution: Verify syntax and ensure arguments are lists

# Wrong
%{"#map" => "{{items}}"}

# Right
%{"#map" => ["{{items}}", item_template]}

Empty Results

Problem: Directive returns empty

Solution: Check collection is rendering correctly

# Debug: render collection first
{:ok, items} = Mau.render("{{$items}}", context)
IO.inspect(items)

Type Errors

Problem: Wrong data type to directive

Solution: Ensure correct types:

  • #map and #filter need lists
  • #merge needs maps
  • #pick needs map and key list

See Also