VOOZH about

URL: https://dev.to/imdj/skip-the-designer-editing-logic-apps-data-mapper-lml-files-directly-40l0

⇱ Skip the Designer — Editing Logic Apps Data Mapper LML Files Directly - DEV Community


The Azure Logic Apps Data Mapper has a visual designer in VS Code. You drag lines between schemas, drop in functions, and it generates an .lml file (YAML) that compiles to XSLT 3.0.

In theory, you never touch the YAML. In practice, the designer has reliability problems that make direct LML editing the better workflow.


Why skip the designer?

The designer doesn't always persist changes correctly:

  • Changes revert on reopen — save, close, reopen, and the .lml on disk has the old value
  • String literals lose inner quotesxpath("'kg'") becomes xpath("kg"), changing a literal to an element reference
  • Expressions get corrupted — conditions like xpath("if (...) then 'Y' else 'N'") lose quote characters
  • Function arguments get rewritten — argument order or paths change silently
  • No undo across sessions — you need git diff to catch silent corruption

These happen with normal operations, not edge cases.


What LML looks like

LML is YAML. Here's a complete map:

$version: 1
$input: XML
$output: XML
$sourceSchema: OrderSchema.xsd
$targetSchema: ShipmentOutput.xsd
$sourceNamespaces:
 ns0: http://schemas.contoso.com/Order
$targetNamespaces:
 xs: http://www.w3.org/2001/XMLSchema
Shipments:
 Shipment:
 ShipmentRef: prefixShipRef(/ns0:Order/ns0:OrderID)
 ShipDate: reformatDate(/ns0:Order/ns0:OrderDate)
 Recipient: /ns0:Order/ns0:Customer/ns0:CustomerName
 Lines:
 $for(/ns0:Order/ns0:Items/ns0:Item):
 $if(xpath("ns0:Quantity >= 1")):
 Line:
 Product: ns0:ProductName
 Quantity: ns0:Quantity

Header declares schemas and namespaces. Body maps target elements (left) to source expressions (right).


LML syntax cheat sheet

# Direct mapping
Recipient: /ns0:Order/ns0:Customer/ns0:CustomerName

# Attribute on parent element
$@shipId: /ns0:Order/ns0:OrderID

# Loop over repeating elements (paths inside are relative)
$for(/ns0:Order/ns0:Items/ns0:Item):
 Line:
 Product: ns0:ProductName

# Conditional
$if(xpath("ns0:Quantity >= 1")):
 Line:
 Product: ns0:ProductName

# Custom function (defined in Functions/*.xml)
ShipDate: reformatDate(/ns0:Order/ns0:OrderDate)

# Raw XPath escape hatch
Priority: xpath("upper-case(@priority)")
WeightUnit: xpath("'kg'") # inner quotes = string literal

Custom extension functions

Defined in Artifacts/DataMapper/Extensions/Functions/*.xml:

<customfunctions>
 <function name="reformatDate" as="xs:string"
 description="Reformats yyyy-MM-dd to dd/MM/yyyy.">
 <param name="dateVal" as="xs:date"/>
 <value-of select="format-date($dateVal, '[D01]/[M01]/[Y0001]')"/>
 </function>
</customfunctions>

Gotchas

Zero-parameter functions crash the compiler. The SDK throws a NullReferenceException and silently skips the entire XML file. Workaround: add a dummy parameter.

Function arguments must be single-step element names. Multi-step paths cause compile errors — wrap them in xpath():

# INVALID
Address: formatAddress(ns0:Customer/ns0:Street, ns0:Customer/ns0:City)

# FIX
Address: formatAddress(xpath("ns0:Customer/ns0:Street"), xpath("ns0:Customer/ns0:City"))

The workflow: edit, compile, verify

1. Edit the YAML

Open the .lml file in any text editor and make your change.

2. Compile to XSLT

Install lml-compile, a dotnet global tool that wraps the Logic Apps SDK:

dotnet tool install -g lml-compile

Compile:

lml-compile Artifacts/MapDefinitions/MyMap-lml.lml Artifacts/Maps/MyMap-lml.xslt
# OK: MyMap-lml.xslt

3. Verify

Run dotnet test or press F5 with the XSLT Debugger extension.


Auto-compile on save

Install the Run on Save extension and add this to your workspace settings (for multi-root workspaces, add it in the .code-workspace file — Run on Save doesn't read folder-level settings):

"emeraldwalk.runonsave":{"commands":[{"match":"\\.lml$","cmd":"lml-compile ${file} ${fileDirname}/../Maps/${fileBasenameNoExt}.xslt"}]}

Note: Run on Save uses its own variables — not VS Code task variables. ${fileBasenameNoExt} and ${fileDirname} work; ${fileBasenameNoExtension} and ${workspaceFolder} do not. Reload the window after adding the config.

Now the cycle is just: edit .lml → save → F5 or dotnet test. The compile happens automatically.

What the compiler catches

  • Malformed YAML / missing $version header
  • Missing schemas in Artifacts/Schemas/
  • Unrecognized function calls
  • Invalid XPath syntax

Errors surface on save instead of at deploy time.


Naming convention

Name hand-authored LML files with a -lml suffix:

Artifacts/MapDefinitions/OrderToShipment-lml.lml → Artifacts/Maps/OrderToShipment-lml.xslt

This avoids colliding with hand-authored XSLT files and makes it clear which files are compiled output.


Summary

Use the designer to scaffold the initial map. Then switch to direct LML editing for all subsequent changes.

Edit .lml → save → auto-compile → verify

No designer round-trip. No silent rewrites. No surprises.


The XSLT Debugger extension is on the VS Code Marketplace for macOS and Windows.