Create and manage JSON structures, import mappings and export mappings in MDL, plus the domain-model shapes they map onto. Use when turning a JSON payload into entities, mapping a REST or queue response, or exporting objects as JSON.
Pro scans all 4 files and shows the line behind each finding
Scanned 10/4/2026
npx -y skills add mendixlabs/mxcli --skill json-structures-and-mappings --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Json Structures And Mappings?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/mendixlabs-json-structures-and-mappings)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: json-structures-and-mappings
description: "Create and manage JSON structures, import mappings and export mappings in MDL, plus the domain-model shapes they map onto. Use when turning a JSON payload into entities, mapping a REST or queue response, or exporting objects as JSON."
---
# JSON Structures, Import Mappings & Export Mappings
This skill covers creating and managing JSON structures, import mappings, and export mappings in Mendix using MDL.
## Key Concepts
### JSON Structures
A JSON structure defines the schema of a JSON payload. It stores a JSON snippet and auto-derives an element tree with paths, types, and custom names.
### Import Mappings
An import mapping converts a JSON string into Mendix entity objects. It maps JSON fields to entity attributes.
#### Two names per member: the raw key and the exposed name
Every JSON structure element stores **both**, and for any lowercase-initial key
they differ:
| | Example | Used for |
|---|---|---|
| **Path** (raw JSON key) | `(Object)\|uuid` | what the **runtime** resolves by |
| **ExposedName** (derived) | `Uuid` | what **Studio Pro displays** |
Mendix derives the exposed name by capitalising the initial, and for an array's
item object by suffixing `Item` — so `total` → `Total`, `camelCase` → `CamelCase`,
`__Value` (array) → `__ValueItem` (its item). Keys already starting with an
underscore are left alone: `__returnedCount` stays `__returnedCount`.
This is **Mendix's own convention, not something mxcli does**. A blank app's
Studio-Pro-authored `FeedbackModule.JSON_AppInsightsResponse` stores
`ExposedName: "Uuid"` against `Path: "(Object)|uuid"`, and its `IMM_PostResponse`
binds `JsonPath: "(Object)|uuid"`.
Consequences worth knowing:
- **Either spelling works in MDL.** `Total = total` and `Total = Total` produce the
same stored mapping. Write whichever you have.
- **`DESCRIBE` emits the raw JSON key**, so its output reproduces the script that
produced the mapping — `Total = total` comes back as `Total = total`, and an
array binding as `= item` rather than `= ItemItem`. It also emits
`create or modify`, so the output re-runs against the project it was read from.
(Until #915 it printed the exposed name and a bare `create`: the text differed
from the input, making every script-vs-describe diff noise, and re-running it
failed with "import mapping already exists". The stored mapping was correct
either way.)
- **A member matching neither spelling is refused**, listing what would have
worked. It is never written with a guessed path: such a mapping passed
`mxcli check` and failed later in mxbuild (CE5015) or at runtime.
#### Inherited attributes
Mendix inheritance is multi-table: all of a parent's attributes are members of the
child, so an entity created with `extends` can map them. Name an inherited
attribute exactly like one of the entity's own — mxcli resolves each to the entity
that **declares** it, which is the reference Studio Pro needs to show the field
mapped.
```sql
mdl 1;
create persistent entity Docs.DocumentBase (
DocName: String(200),
Confidential: Boolean
);
create persistent entity Docs.Contract extends Docs.DocumentBase (
ContractNumber: String(50)
);
create import mapping Docs.IMM_Contract
with json structure Docs.JSON_Contract
{
create Docs.Contract {
ContractNumber = contractNumber, -- own
DocName = docName, -- inherited
Confidential = confidential -- inherited
}
};
```
Qualifying an inherited attribute against the entity being mapped instead of its
declaring entity is Mendix **CE1613** "The selected attribute ... no longer
exists", and the field shows unmapped in Studio Pro.
## Export Mappings
An export mapping converts Mendix entity objects into a JSON string. It maps entity attributes to JSON fields.
### Critical: Import and Export Need Different Domain Models
**Import and export mappings for the same JSON structure typically require different entity structures.**
- **Import**: The child entity owns the FK to the parent (`from Child to Parent`). Arrays map directly to the item entity — no intermediate container entity needed.
- **Export**: The domain model mirrors the JSON structure. Arrays need an intermediate container entity (e.g., `Items`) plus an item entity (e.g., `ItemsItem`). The container links to the parent, the item links to the container.
---
## JSON Structures
### Create
```sql
mdl 1;
create json structure Module.JSON_Pet
sample '{"id": 1, "name": "Fido", "status": "available"}';
```
For multi-line JSON, use dollar-quoting:
```sql
mdl 1;
create json structure Module.JSON_Order
sample $${
"orderId": 100,
"customer": {"name": "Alice", "email": "alice@example.com"},
"items": [{"sku": "A1", "quantity": 2, "price": 9.99}]
}$$;
```
Custom name mapping (rename JSON fields):
```sql
mdl 1;
create json structure Module.JSON_Pet
sample '{"id": 1, "name": "Fido"}'
CUSTOM NAME map ('id' as '_id');
```
**Name array items yourself** — `item of` (ako/mxcli#272). An array's item is the
anonymous `[...]` entry, so it has no JSON key and the plain form cannot reach
it; left alone it gets a derived name like `LinesItem`:
```sql
mdl 1;
create json structure Module.JSON_Invoice
sample '{"lines": [{"sku": "A1"}], "tags": ["urgent"]}'
CUSTOM NAME map (
'lines' as 'OrderLines',
item of 'lines' as 'OrderLine',
item of 'tags' as 'Tag'
);
```
This is worth doing rather than accepting the default: a **mapping element clones
the schema element's ExposedName**, so the item's name is what every mapping over
this structure carries, and it is one of the two names a member resolves by.
- The two clauses are independent — naming an item does not require renaming its
array, so adding one is a one-line diff.
- `item of` names a primitive array's **wrapper** too; that wrapper *is* the item.
- A root-level array has no key: `item of 'Root' as 'Entry'`.
- An entry whose key is not in the snippet is an error (`MDL-JSON01`), as is
`item of` on something that is not an array (`MDL-JSON02`).
### Browse
```sql
mdl 1;
list json structures;
list json structures in module;
describe json structure Module.JSON_Pet;
drop json structure Module.JSON_Pet;
```
## Message Definitions
A mapping's source can also be a **message definition** — 74 of the 327 mappings
in the demo corpus (22.6%), and the only non-JSON source MDL can create. It
holds nothing external: it is a **selection over the domain model**.
```sql
mdl 1;
create message definition collection Sales.MD_Order {
definition OrderMessage for Sales.Order as 'Orders' {
OrderId,
Sales.Order_Customer/Sales.Customer { FirstName }
}
};
```
A bare name is an attribute; `Assoc/Module.Entity` is an association. **Name the
target entity** — the stored cardinality follows the direction of traversal and
the association's type, so a `Reference` gives a single object one way and a list
the other, while a `ReferenceSet` is a list both ways.
The full vocabulary, the ALTER statements, inherited attributes and what mxcli
deliberately does not guess:
[reference/message-definitions.md](reference/message-definitions.md).
## Import Mappings
### Domain Model for Import
For import mappings, associations point FROM the child entity TO the parent:
```sql
mdl 1;
create non-persistent entity Module.OrderResponse (
OrderId: integer
);
create non-persistent entity Module.CustomerInfo (
Name: string,
Email: string
);
create non-persistent entity Module.OrderItem (
Sku: string,
Quantity: integer,
Price: decimal
);
-- Child entity owns the FK (FROM child TO parent)
create association Module.CustomerInfo_OrderResponse
from Module.CustomerInfo
to Module.OrderResponse;
create association Module.OrderItem_OrderResponse
from Module.OrderItem
to Module.OrderResponse;
```
### Simple Import Mapping (flat JSON)
```sql
mdl 1;
create import mapping Module.IMM_Pet
with json structure Module.JSON_Pet
{
create Module.PetResponse {
PetId = id,
Name = name,
status = status
}
};
```
### Nested Import Mapping (objects and arrays)
Arrays map directly to the item entity — no intermediate container needed:
```sql
mdl 1;
create import mapping Module.IMM_Order
with json structure Module.JSON_Order
{
create Module.OrderResponse {
OrderId = orderId,
create Module.CustomerInfo_OrderResponse/Module.CustomerInfo = customer {
Name = name,
Email = email
},
create Module.OrderItem_OrderResponse/Module.OrderItem = items {
Sku = sku,
Quantity = quantity,
Price = price
}
}
};
```
### Object Handling
Mendix stores **two** properties here, not one: how to get the object, and what
to do when a `find` comes up empty. Both are yours to choose.
| Syntax | Meaning |
|--------|---------|
| `create Module.Entity` | Always create a new object (default) |
| `find Module.Entity or create` | Find by KEY, create one if not found |
| `find Module.Entity or error` | Find by KEY, fail the import if not found |
| `find Module.Entity or ignore` | Find by KEY, skip the element if not found |
| `find or create Module.Entity` | The older spelling of `find … or create` |
Append `overridable` to let the caller override the backup at import time:
`find Module.PetResponse or create overridable`.
```sql
mdl 1;
create import mapping Module.IMM_UpsertPet
with json structure Module.JSON_Pet
{
find Module.PetResponse or create {
PetId = id key,
Name = name,
status = status
}
};
```
**A bare `find` is refused.** Which of the three you get is a real runtime
difference, and mxcli used to pick `create` for you whatever the document said —
so it now asks rather than guessing.
**A `find` has two requirements, and mxcli check enforces both** (ako/mxcli#253):
1. **At least one member marked `key`**, per searching element — nested ones
included. Without it there is nothing to search on: **CE0250**, reported as
`MDL-MAP02`. (`key` is only valid with `find`; on a `create` it means nothing.)
2. **A persistable entity.** A search is a database query, and a non-persistent
entity has no database: **CE0251**, reported as `MDL-MAP03`. Persistability
comes from the **generalization chain**, not the entity's own flag — an entity
declared with plain `create entity` that extends a non-persistent parent is
still not searchable.
Re-measuring these is easy to get wrong: mxbuild reports **one at a time**. A
keyless `find` over a non-persistent entity is CE0250 only, and CE0251 appears
only once a key exists.
A **custom handler is exempt from both** — the microflow *is* the find, so there
is no key to declare and no query to run.
### Custom Object Handling and the Mapping's Input Object
A microflow can resolve the object instead of Create/Find. Write it as `by` on
the element; the microflow's parameters are named with their sources:
| Source | Means |
|--------|-------|
| `parent` | the enclosing mapped object |
| `parameter` | the mapping's own input object |
| `parent(2)` | an ancestor N levels up |
| `a/b/c` | a value from the payload, addressed like any other member |
`parameter` needs the mapping to declare an input object, which is a clause on
the header — import mappings only:
```sql
mdl 1;
create import mapping Module.IMM_Embed
with json structure Module.JSON_Embed
parameter GenAICommons.ChunkCollection
{
create GenAICommons.ChunkCollection {
Name = id,
find Module.Chunk_ChunkCollection/GenAICommons.Chunk
by Module.MF_FindChunk ( Collection: parameter, Index: idx )
= embeddings {
Text = text
}
}
};
```
Using `parameter` without declaring one is refused — the build reports it as
CE0279. The declared entity must match the microflow's parameter type, which the
build checks as CE0282.
---
## Export Mappings
### Domain Model for Export
Export mappings require entities that **mirror the JSON structure**. Arrays need an intermediate container entity:
```sql
mdl 1;
-- Root entity (matches top-level JSON object)
create non-persistent entity Module.ExRoot (
OrderId: integer
);
-- Nested object entity (1-1 relationship, use OWNER Both)
create non-persistent entity Module.ExCustomer (
Name: string,
Email: string
);
-- Array CONTAINER entity (no attributes, just links parent to items)
create non-persistent entity Module.ExItems;
-- Array ITEM entity (attributes for each array element)
create non-persistent entity Module.ExItemsItem (
Sku: string,
Quantity: integer,
Price: decimal
);
-- Associations: child FROM, parent TO
create association Module.ExCustomer_ExRoot
from Module.ExCustomer
to Module.ExRoot
owner both; -- 1-1 for nested objects
create association Module.ExItems_ExRoot
from Module.ExItems
to Module.ExRoot; -- 1-* for arrays
create association Module.ExItemsItem_ExItems
from Module.ExItemsItem
to Module.ExItems; -- 1-* for array items
```
### Simple Export Mapping (flat JSON)
```sql
mdl 1;
create export mapping Module.EMM_Pet
with json structure Module.JSON_Pet
{
Module.PetResponse {
id = PetId,
name = Name,
status = status
}
};
```
### Nested Export Mapping (objects and arrays)
Arrays have TWO levels: container entity + item entity:
```sql
mdl 1;
create export mapping Module.EMM_Order
with json structure Module.JSON_Order
{
Module.ExRoot {
orderId = OrderId,
Module.ExCustomer_ExRoot/Module.ExCustomer as customer {
name = Name,
email = Email
},
Module.ExItems_ExRoot/Module.ExItems as items {
Module.ExItemsItem_ExItems/Module.ExItemsItem as ItemsItem {
sku = Sku,
quantity = Quantity,
price = Price
}
}
}
};
```
### NULL VALUES option
```sql
create export mapping Module.EMM_Pet
with json structure Module.JSON_Pet
null values SendAsNil -- or LeaveOutElement (default)
{
...
};
```
---
## Starting a Mapping Below the Payload Root
A mapping does not have to start at the top of the JSON. `root a/b/c` on the
source clause selects the element it starts at, and the path may pass **through
arrays** — the mapping is then rooted at the array's item, so it yields one
object per entry.
```sql
mdl 1;
create import mapping RootDemo.IMM_Choices
with json structure RootDemo.JSON_Completion root response/choices/message
{ create RootDemo.Message { Role = role, Content = content } };
```
Worked examples, the array-crossing rule and what it does to a call's
cardinality: [reference/mapping-root-selection.md](reference/mapping-root-selection.md).
## Microflow Actions
### Import from Mapping (JSON → entities)
```sql
-- With result variable (non-persistent entities)
$PetResponse = import from mapping Module.IMM_Pet($JsonContent);
-- Without result variable (persistent entities, stores to DB)
import from mapping Module.IMM_Pet($JsonContent);
```
#### Range — how much of the result to bind
Optional trailing clause, matching Studio Pro's **All / First / Custom** setting
on the activity. Omitting it means **All**; whether the variable is an object or
a list is inferred from the mapping's own root shape, as it always has.
`describe` leaves `all` off an object result (writing it there reads as "returns
a list") and prints it for a list result — the two spellings store the same
activity.
```sql
$Pets = import from mapping Module.IMM_Pets($Json) all; -- All (the default)
$Pet = import from mapping Module.IMM_Pets($Json) first; -- First: ONE object
$Page = import from mapping Module.IMM_Pets($Json) limit 10; -- Custom
$Page = import from mapping Module.IMM_Pets($Json) limit 10 offset 5;
```
`first` is a separate word from `limit 1` on purpose: `limit 1` is a *list* of
one, `first` binds a single *object*, so the result variable's type differs.
Two things the range does **not** do:
- **It does not change what the mapping returns.** An object-rooted mapping
binds an object under every range — `all` on one is Studio Pro's own default,
and the blank app ships one (`FeedbackModule.SUB_Feedback_PostToAppInsights`).
Only `first` narrows a list mapping to a single object.
- **`offset` is not accepted everywhere.** Mendix rejects it with
**CE6100** ("This entity does not support offset") unless the mapping's root
is a list; `limit` alone is fine either way. Verified on mxbuild 11.6.6.
- **`first` is not for an object-rooted mapping.** It narrows a list; on a
mapping that already returns one object it builds clean (`mx check`: 0
errors) and the activity **throws at runtime**. `mxcli check` refuses it,
and `offset` on such a mapping, as **MDL-MAP04** — drop the range.
### Export to Mapping (entity → JSON)
```sql
$JsonOutput = export to mapping Module.EMM_Pet($PetResponse);
```
### Complete Pipeline
```sql
mdl 1;
create microflow Module.ProcessData ()
begin
declare $json string = $latestHttpResponse/content;
$PetResponse = import from mapping Module.IMM_Pet($json);
-- Process...
$Output = export to mapping Module.EMM_Pet($PetResponse);
log info node 'Integration' 'Result: ' + $Output;
end;
```
---
## Browse
```sql
list import mappings [in module];
list export mappings [in module];
describe import mapping Module.Name;
describe export mapping Module.Name;
drop import mapping Module.Name;
drop export mapping Module.Name;
```
---
## Export Workflow: PE → NPE → JSON
Export mappings work on non-persistent entity (NPE) structures that mirror the target JSON. When the source data is in persistent entities (PE) in the database, the typical workflow is:
1. **Retrieve** persistent data from the database
2. **Build NPE tree** in a microflow: create NPE objects, set attributes, link via associations to match the JSON structure
3. **Export to mapping** to serialize the NPE tree to JSON
```sql
mdl 1;
-- Example: build NPE tree from persistent Order data, then export
create microflow Module.ExportOrder ($Order: Module.Order)
returns string as $json
begin
-- Build the NPE tree matching the JSON structure
$Root = create Module.ExRoot (OrderId = $Order/OrderId);
retrieve $Customer from $Order/Module.Order_Customer;
$ExCust = create Module.ExCustomer (Name = $Customer/Name, Email = $Customer/Email);
-- Link customer to root...
-- Export
$json = export to mapping Module.EMM_Order($Root);
return $json;
end;
```
### Shortcut with View Entities
View Entities (OQL-backed) can retrieve data directly into the export-ready structure, skipping the manual NPE assembly:
```sql
mdl 1;
create view entity Module.ExOrderView (
OrderId: integer,
CustomerName: string,
CustomerEmail: string
) as (
select o.OrderId as OrderId, c.Name as CustomerName, c.Email as CustomerEmail
from Module.Order as o
join o/Module.Order_Customer/Module.Customer as c
);
```
This can reduce the microflow to a single retrieve + export step.
---
## Realistic Example: Countries REST API
One worked example — structures, import of a single object and of a list, export
in both directions, and the microflow that ties them together — is in
[`reference/rest-api-example.md`](reference/rest-api-example.md).
## Placing Documents in Folders
Every one of these documents takes a `folder` clause on `create`, straight after
the qualified name. Missing folders in the path are created:
```mdl
mdl 1;
create json structure Sales.JSON_Order folder 'Private/JSON structures'
sample '{"id": 1, "total": 9.99}';
create import mapping Sales.IMM_Order folder 'Private/Import mappings'
with json structure Sales.JSON_Order
{
create Sales.Order { OrderId = id, Total = total }
};
```
On `create or modify` the clause **moves** an existing document. Omitting it
leaves placement alone — it never returns a document to the module root — so
adding a folder to an existing script is safe and removing one is a no-op.
`describe` emits the clause, so a description replays into the same folder.
See `organize-project` for `move` and the full folder story.
## Common Mistakes
| Mistake | Fix |
|---------|-----|
| Reusing import domain model for export | Export needs separate entities mirroring JSON structure |
| Association direction wrong | Always FROM child TO parent (child owns FK) |
| Using `owner default` for 1-1 nested objects in export | Use `owner both` for 1-1 relationships |
| Missing array container entity in export | Arrays need Container + Item entities |
| Using `key` with `create` handling | `key` only valid with `find` |
| `find` without `or create` / `or error` / `or ignore` | Say what happens when the object is not found — the three differ at runtime |
| `find` with no member marked `key` (MDL-MAP02) | Mark the identifying member — a search needs something to search on (CE0250) |
| `find` over a non-persistent entity (MDL-MAP03) | Use `create`, or make the entity persistent — a search is a database query (CE0251) |
| `Param: parameter` with no `parameter Module.Entity` on the header | Declare the mapping's input object, or the build reports CE0279 |
| `parameter` on an EXPORT mapping | Export mappings have no input object — their parameter is the root object |
| Arrays in import with container entity | Import arrays map directly to item entity, no container |
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!