Data Mapping Documentation
ParseMapper format for mapping and transforming data from various sources
[navigate:/devdoc/test-mapper|Test Mapper|play|right]
Overview
The ParseMapper ‘import’ format is a configuration syntax for mapping and transforming data from various structured sources (such as XML, JSON, CSV, etc.) into a normalized internal representation. This format is used to define how fields from the source data are mapped, transformed, and related to target entities.
Key Components
Variable Definitions Optional variable declarations using @variable directive
Type Declaration Specifies the source data type (XML, JSON, CSV, etc.)
Global Directives Optional settings that affect parsing behavior
Entity Mappings Definitions for extracting and mapping entities and fields
File Structure
A typical import mapping file consists of variable definitions, type declaration, global directives, and entity mappings:
@variable default_vat_rate = {
name: "Default VAT Rate"
type: "number"
defaultValue: "21"
}
type XML
@atomic = "false"
@strict = false
@language = "en"
products = '$.SHOP.SHOPITEM[*]' {
id[code] = 'CODE'
name = 'NAME'
// ... more field mappings ...
variants = 'VARIANTS.VARIANT' {
code = 'CODE'
// ... nested field mappings ...
@removeAll = "true"
}
// ... more nested entities ...
}
Path Syntax
Simple Dotted Path
Straightforward dot notation without leading $ or @.
LOGISTIC.WEIGHT
Used for direct, static field access
JSONPath
Powerful syntax for navigating JSON structures. Denoted by leading $ or @.
$.SHOP.SHOPITEM[*]
Supports advanced selectors, wildcards, and array access
Parent Reference ($parent) [badge:New|secondary|right]
Access fields from the parent object in nested structures using the $parent prefix.
$parent.orderId // Access parent field
$parent.customer.name // Access nested parent field
$parent["field-name"] // With special characters
Example usage:
orders = '$.orders[*]' {
orderId = '$.id'
items = '$.items[*]' {
productName = '$.name'
orderId = '$parent.id'
orderDate = '$parent.date'
}
}
Useful when child records need to reference parent-level information
Multiple Path Alternatives
Use the || operator to specify fallback paths. The first path that yields a value will be used.
name = '$.NAME' || '$.ALT_NAME' || "STATIC"
Syntax Reference
1. Type Declaration
Defines the source data type:
type XML
Supported types: XML, JSON, CSV, YAML, XLSX, XLS
2. Global Directives
Control parser behavior:
@atomic = "false"
@strict = false
@language = "en"
- @atomic: Whether the import should be atomic
- @strict: Whether to enforce strict mapping
- @language: Default language for imports
3. Variable Definitions
Define variables for configurable import parameters:
@variable default_vat_rate = {
name: "Default VAT Rate"
description: "Variable description"
type: "number"
defaultValue: "21"
}
Variable Types
- “number” - Numeric values
- “string” - Text values
- “boolean” - True/false
- “relation” - Entity references
Translatable Fields
name:
- en: "English Name"
- cs: "Czech Name"
4. Entity Mapping
Define how to extract entities from source data:
entityName = 'jsonpath' {
field1 = 'SOURCE_FIELD'
field2 = 'NESTED.PATH'
manufacturer[code] = 'MANUFACTURER'
status = "active"
// ... more fields ...
}
Special Features
- Lookup fields: field[key] = ‘SOURCE’
- Static values: field = “static”
- Nested entities with @removeAll
- Field replacement tables
- Path-specific replacement tables
Expressions
total = ${price + vat}
Examples
E-shop XML Import
Complex XML import with nested entities and lookup fields:
type XML
@atomic = "false"
@strict = false
products = '$.SHOP.SHOPITEM[*]' {
id[code] = 'CODE'
name = 'NAME'
description = 'DESCRIPTION'
unit[name] = 'UNIT'
manufacturer[code] = 'MANUFACTURER'
variants = 'VARIANTS.VARIANT' {
code = 'CODE'
priceWithVat = 'PRICE_VAT'
@removeAll = "true"
}
images = 'IMAGES.IMAGE' {
url = '_'
description = '$["$"].description'
@removeAll = "true"
}
categories = 'CATEGORIES' {
category[id] = 'CATEGORY.$.id'
category[name] = 'CATEGORY._'
@removeAll = "true"
}
}
Field Replacement Example
Map specific source values to different target values:
taxRateLevel = 'TAX_RATE_LEVEL' ['high' = 'standard', 'low' = 'reduced']
status = 'state' ['active' = 'enabled', 'inactive' = 'disabled']
Path-Specific Replacement Tables [badge:New|secondary|right]
Apply replacement tables to individual paths in multipath expressions:
// Mixed: No replacement on first path, replacement on second path
visibility = 'VISIBILITY' || ('VISIBLE' ['1' = 'visible', '0' = 'hidden'])
// Individual: Different replacement tables for each path
status = ('STATUS' ['A' = 'active', 'I' = 'inactive']) || ('STATE' ['1' = 'enabled', '0' = 'disabled'])
// Backward compatible: Field-level replacement table (existing behavior)
type = 'TYPE' || 'TYPE_CODE' ['premium' = 'PREMIUM', 'basic' = 'STANDARD']
Mixed Global/Local Apply replacement only to specific paths while leaving others unchanged
Individual Path Tables Each path can have its own transformation rules
Backward Compatible Existing field-level syntax continues to work
Translatable Fields with Dynamic Language Keys [badge:New|secondary|right]
Map multi-language content with dynamic language key resolution
Translatable fields can determine the target language dynamically from the source data, enabling efficient multi-language imports from single source records.
Simple Path Language Key
name['LANG'] = 'TEXT'
Language field in the same record determines the language
JSONPath Language Key
name['$.language.code'] = 'TEXT'
Use complex path expressions to locate language identifier
Static Language Key
name["en"] = 'NAME_EN' || 'NAME'
Explicitly map to specific language with fallback
Expression Language Key
name[${LOWER(langCode)}] = 'TEXT'
Computed language key using expressions
Complete Example
products = '$.items[*]' {
code = 'CODE'
// Dynamic language keys from source data
name['LANG'] = 'TEXT' @loop '$.TEXTS[*]'
description['LANG'] = 'DESCRIPTION' @loop '$.TEXTS[*]'
// Static language keys with fallback
shortName["en"] = 'SHORT_NAME_EN' || 'NAME'
shortName["cs"] = 'SHORT_NAME_CS' || 'NAME'
}
@loop Directive [badge:New|secondary|right]
Iterate over arrays to create multiple records or translations
The @loop directive allows you to iterate over arrays or collections in the source data, creating multiple records, translations, or field values.
Basic Syntax
fieldName = 'PATH' @loop 'ITERATION_PATH'
With Translatable Fields
name['lang'] = 'text' @loop '$.translations[*]'
Create multiple language translations from array
Array Creation
tags = 'tag' @loop '$.tags[*]'
Create array field from source array
Nested Relations with @loop
images = '$.images[*]' @loop '$.images[*]' {
url = 'url'
description['lang'] = 'alt' @loop '$.alts[*]'
sortOrder = 'position'
}
Loop over images array to create multiple image records, each with translated descriptions
Complete Example
products = '$.products[*]' {
code = 'sku'
// Multi-language from translations array
name['languageCode'] = 'title' @loop '$.translations[*]'
description['languageCode'] = 'desc' @loop '$.translations[*]'
// Simple array creation
tags = 'tag' @loop '$.tags[*]'
// Nested relations
variants = '$.options[*]' @loop '$.options[*]' {
code = 'optionSku'
name['lang'] = 'optionName' @loop '$.optionTranslations[*]'
priceWithVat = 'optionPrice'
}
}
Translatable Field Output Formats [badge:Important|secondary|right]
Two supported formats for translatable field output
The mapper supports two output formats for translatable fields, depending on how you structure your mapping.
Object Format (with @loop) [badge:Recommended|default|right]
// Mapper syntax
name['LANG'] = 'TEXT' @loop 'TEXTS.ITEM'
// Output
{
"name": {
"en": "English Name",
"cs": "České jméno",
"de": "Deutscher Name"
}
}
✅ Clean, flat structure ✅ Better performance for lookups ✅ Easier to work with in UI ✅ Recommended for most use cases
Array Format (without @loop) [badge:Alternative|outline|right]
// Mapper syntax
name = 'TEXTS.ITEM' {
language = 'LANG'
value = 'TEXT'
}
// Output
{
"name": [
{"language": "en", "value": "English Name"},
{"language": "cs", "value": "České jméno"},
{"language": "de", "value": "Deutscher Name"}
]
}
• Structured metadata format • Can include additional fields • Used for array-based systems • Legacy compatibility
Complete Example: Both Formats
type XML
products = '$.SHOP.PRODUCT[*]' {
code = 'CODE'
// Object format - recommended
name['$.language'] = 'title' @loop 'DESCRIPTIONS.DESC'
description['$.language'] = 'text' @loop 'DESCRIPTIONS.DESC'
// Array format - alternative
alternativeName = 'DESCRIPTIONS.DESC' {
language = '$.language'
value = 'title'
}
}
When to Use Each Format
Use Object Format (@loop):
- Most common scenario - clean translations
- Better UI integration and performance
- Standard approach for new projects
Use Array Format (nested):
- Need to store metadata with translations
- Migrating from array-based systems
- Additional fields beyond language/value
@if Directive [badge:New|secondary|right]
Conditionally include fields based on runtime conditions
The @if directive allows you to conditionally include fields or relations based on runtime conditions evaluated from the source data.
Basic Syntax
fieldName = 'PATH' @if ${condition}
Simple Condition
discount = 'DISCOUNT' @if ${hasDiscount == 'true'}
Only include discount if product has one
Complex Condition
price = 'BULK_PRICE' @if ${isB2B == 'true' && price > 100}
Multiple conditions with AND/OR operators
Supported Operators
Comparison
==, !=, >, <, >=, <=
Logical
&& (AND), || (OR)
Types strings, numbers, booleans
Conditional Nested Relations
// Only create images if product has them
images = '$.images[*]' @loop '$.images[*]' @if ${hasImages == 'true'} {
url = 'url'
description = 'alt'
sortOrder = 'position'
}
Complete Example
products = '$.products[*]' {
code = 'sku'
name = 'title'
basePrice = 'price'
// Conditional fields
discount = 'discountPercent' @if ${hasDiscount == 'true'}
weight = 'weightKg' @if ${isPhysical == 'true'}
downloadUrl = 'fileUrl' @if ${isDigital == 'true'}
// Extended description only for premium products
extendedDesc = 'detailedText' @if ${productType === 'premium'}
// Conditional relations
variants = '$.options[*]' @loop '$.options[*]' @if ${isConfigurable == 'true'} {
code = 'optionSku'
priceWithVat = 'optionPrice'
}
}
Combined @loop and @if [badge:Advanced|secondary|right]
Combine directives for powerful conditional array processing
You can combine @loop and @if directives for sophisticated conditional array processing and filtering.
Conditional Multi-Language Import
products = '$.items[*]' {
code = 'CODE'
// Import translations only if available
name['LANG'] = 'TEXT' @loop '$.TEXTS[*]' @if ${HAS_TRANSLATIONS == 'true'}
description['LANG'] = 'DESC' @loop '$.TEXTS[*]' @if ${HAS_TRANSLATIONS == 'true'}
// Fallback if no translations
name = 'NAME' @if ${HAS_TRANSLATIONS != 'true'}
description = 'DESCRIPTION' @if ${HAS_TRANSLATIONS != 'true'}
}
Filtered Array Processing
products = '$.products[*]' {
code = 'sku'
name['lang'] = 'title' @loop '$.translations[*]'
// Only import images if product has them
images = '$.images[*]' @loop '$.images[*]' @if ${hasImages == 'true'} {
url = 'url'
description['lang'] = 'alt' @loop '$.alts[*]'
sortOrder = 'position'
}
// Specifications only for electronics
specifications = '$.specs[*]' @loop '$.specs[*]' @if ${category === 'electronics'} {
name['lang'] = 'specName' @loop '$.nameTranslations[*]'
value = 'specValue'
unit = 'specUnit'
}
}
Best Practices
- Use @if before @loop when possible to filter early
- Keep conditions simple and testable
- Test with representative sample data
- Avoid deeply nested loops (max 2-3 levels)
- Document complex business logic with comments
Array Syntax for Collection Fields [badge:New|secondary|right]
Append multiple values to collection fields from independent sources
The array syntax (fieldName[]) allows you to append multiple values to the same collection field, rather than replacing it. This is particularly useful when you need to populate a field from multiple independent sources or conditions.
Basic Syntax
fieldName[] = 'PATH' {
// field mappings
}
When you use the [] suffix on a field name, each occurrence appends to the collection rather than replacing previous values.
Problem: Multiple Assignments
Without array syntax, you cannot assign to the same field multiple times:
❌ Not Supported - Parser Error
flags = 'LABELS.LABEL' {
flag[code, name] = 'NAME'
}
flags = {
flag[code] = 'new'
} @if ${NEW_YN === '1'}
flags = {
flag[code] = 'special'
} @if ${SPECIAL_YN === '1'}
Solution: Array Syntax
✅ Correct - Appends to Collection
// First clear existing flags
flags = 'LABELS.LABEL' {
flag[code, name] = 'NAME'
@removeAll = "true"
}
// Then append conditionally
flags[] = {
flag[code] = 'new'
} @if ${NEW_YN === '1'}
flags[] = {
flag[code] = 'special'
} @if ${SPECIAL_YN === '1'}
flags[] = {
flag[code] = 'sellout'
} @if ${SELLOUT_YN === '1'}
Complete Example with @removeAll
A common pattern is to clear existing data first, then append new items:
products = '$.SHOP.SHOPITEM[*]' {
code = 'CODE'
// First, clear all existing flags and import from primary source
flags = 'LABELS.LABEL' {
flag[code, name] = 'NAME'
@removeAll = "true"
}
// Then append additional flags based on conditions
flags[] = {
flag[code] = 'new'
flag[name] = 'New Product'
} @if ${NEW_PRODUCT === 'true'}
flags[] = {
flag[code] = 'clearance'
flag[name] = 'Clearance Sale'
} @if ${CLEARANCE === 'true'}
}
Array Syntax with Simple Values
products = '$.products[*]' {
code = 'id'
// Base tags from primary field
tags[] = 'primaryTag'
// Add category-based tags
tags[] = 'electronics' @if ${category === 'ELECTRONICS'}
tags[] = 'apparel' @if ${category === 'CLOTHING'}
// Add promotional tags
tags[] = 'featured' @if ${isFeatured == 'true'}
tags[] = 'bestseller' @if ${salesRank <= 10}
}
When to Use Array Syntax
✅ Use Array Syntax When:
- Multiple independent sources
- Conditional additions
- Incremental building
- Mixed static and dynamic data
❌ Don’t Use When:
- Single source (use @loop)
- Want to replace rather than append
- Simple single assignment
Best Practices
- Clear First, Then Append: Use @removeAll on the first assignment, then use [] for subsequent additions
- Use @if for Conditional Items: Combine with @if directive to add items only when conditions are met
- Maintain Sort Order: Consider adding explicit sort order fields when using array syntax with multiple sources
- Test Thoroughly: Verify that all array items are created correctly with sample data
Lookup Field Directives [badge:New|secondary|right]
Control behavior and capture missing data when resolving lookup fields
When mapping lookup fields (relations), these directives control what happens when a reference cannot be resolved and allow you to collect data for creating missing records.
@if-not-found Directive
Controls the behavior when a lookup field reference cannot be found in the database.
skip Skip the entire record (default)
create Collect data for creating missing records
ignore Leave field null, continue processing
error Throw error, halt import
products = '$.SHOPITEM[*]' {
code = 'CODE'
name = 'NAME'
// Skip entire product if manufacturer not found (default)
manufacturer[code] = 'MANUFACTURER_CODE'
// Collect data to create missing categories
category[code]@if-not-found = 'create' = 'CATEGORY_ID'
// Continue even if unit not found (field will be null)
unit[name]@if-not-found = 'ignore' = 'UNIT_NAME'
}
@create-as Directive
When using @if-not-found='create', this directive specifies what data to collect for creating missing lookup records.
products = '$.SHOPITEM[*]' {
code = 'CODE'
name = 'NAME'
// Collect category data for creation if not found
category[code]@if-not-found = 'create' = 'CATEGORY_ID'
category[code]@create-as = {
code = 'CATEGORY_ID'
name = 'CATEGORY_TITLE'
description = 'CATEGORY_DESC' || 'CATEGORY_TITLE'
sortOrder = 'CATEGORY_ORDER'
}
}
Advanced Examples
Alternative Paths and Replace Tables
category[code]@if-not-found = 'create' = 'CATEGORY_ID' || 'CATEGORY_CODE'
category[code]@create-as = {
code = 'CATEGORY_ID' || 'CATEGORY_CODE'
name = 'CATEGORY_NAME' || 'CATEGORY_TITLE' || 'CATEGORY_ID'
type = 'CATEGORY_TYPE' ['MAIN' = 'primary', 'SUB' = 'secondary']
}
Static Values
manufacturer[code]@if-not-found = 'create' = 'MANUFACTURER_CODE'
manufacturer[code]@create-as = {
code = 'MANUFACTURER_CODE'
name = 'MANUFACTURER_NAME'
country = "CZ"
isActive = "true"
}
Nested Relations
products = '$.SHOPITEM[*]' {
code = 'CODE'
name = 'NAME'
category[code]@if-not-found = 'create' = 'CATEGORY_ID'
category[code]@create-as = {
code = 'CATEGORY_ID'
name = 'CATEGORY_NAME'
// Nested relations in create-as block
productPrices = 'PRICES.PRICE' {
priceWithVat = 'PRICE_VAT'
priceWithoutVat = 'PRICE_BASE'
validFrom = 'DATE_FROM'
@removeAll = "true"
}
}
}
How It Works
- During import, the system tries to resolve each lookup field reference
- If not found and
@if-not-found='create', data is collected - Collected data is stored with key
fieldName@create-as - The import job contains both imported records and creation data
- You can then create missing records and re-run the import
Best Practices
- Use
createfor critical references like categories, manufacturers - Use
ignorefor optional fields that can be filled later - Use
skipwhen missing references indicate invalid data - Use
errorwhen data quality must be guaranteed - Provide alternative paths and fallbacks in
@create-asblocks - Include enough fields to create valid records (check metamodel requirements)
- Test with data containing missing references
Parent Reference ($parent) [badge:New|secondary|right]
Access parent object fields in nested structures
When working with nested entities, you can reference fields from the parent object using the $parent prefix. This is particularly useful when child records need to include information from their parent level.
Syntax
$parent.field // Direct parent field
$parent.nested.field // Nested parent field
$parent["field-name"] // With special chars
Example: Order Items with Parent Order Info
type JSON
orders = '$.orders[*]' {
orderId = '$.id'
orderDate = '$.date'
customer = '$.customer'
items = '$.items[*]' {
productName = '$.name'
price = '$.price'
quantity = '$.quantity'
// Reference parent order fields
orderId = '$parent.id'
orderDate = '$parent.date'
orderCustomer = '$parent.customer'
}
}
Each item will include orderId, orderDate, and orderCustomer from the parent order object.
Example: Product Variants with Parent Product Info
products = '$.products[*]' {
productId = '$.id'
productName = '$.name'
category = '$.category'
brand = '$.brand'
variants = '$.variants[*]' {
variantCode = '$.sku'
variantSize = '$.size'
variantColor = '$.color'
stockLevel = '$.stock'
// Include parent product information
productId = '$parent.id'
productName = '$parent.name'
category = '$parent.category'
brand = '$parent.brand'
}
}
Example: Invoice Line Items
invoices = '$.invoices[*]' {
invoiceNumber = '$.number'
invoiceDate = '$.date'
currency = '$.currency'
customer[code] = '$.customer.code'
lineItems = '$.lines[*]' {
itemCode = '$.code'
description = '$.description'
amount = '$.amount'
quantity = '$.qty'
// Reference parent invoice data
invoiceNumber = '$parent.number'
invoiceDate = '$parent.date'
currency = '$parent.currency'
customerCode = '$parent.customer.code'
}
}
Important Notes
$parentrefers to the immediate parent object in the mapping hierarchy- Parent context is only available in nested structures (objects with nested field mappings)
- If used at the root level where no parent exists, the field will be undefined
- In
@loopdirectives, the loop item becomes the current source, and the original source becomes the parent - You can access nested parent fields:
$parent.customer.name
Use Cases
Denormalization Include parent information in child records for easier querying and reporting
Data Enrichment Add context from parent entities to make child records self-contained
Reference Tracking Maintain references between related records for data integrity
Audit Trail Include parent-level timestamps and identifiers in child records
Tips and Best Practices
Syntax Tips
- Use single quotes for source paths
- Use double quotes for static values
- Use nested blocks for arrays/objects
- Add @removeAll for nested entities
- Use JSONPath syntax ($.path[*]) in @loop
- Use $parent to reference parent fields in nested structures
- Keep @if conditions simple
Performance
- Test with large datasets
- Use lookup fields for relations
- Plan for error handling
- Document complex transformations
- Use @if to filter early in processing
- Minimize nesting depth for loops