Skip to main content

DTO Mapping & Extras

When the user confirms their booking, the form engine builds a FormDto from the session's answers. This page explains how form field answers are routed to the DTO and what happens with extra contextual information.

How DTO Building Works​

For each answered field, the engine determines where to put the value:

Answer → look up fieldDefinition → get dtoField (or use field name)
├── dtoField is in defaults.dtoFields list → FormDto.fields (main DTO)
└── dtoField is NOT in the list → FormDto.extras (keyed by field name)

After all answers are routed, conversationExtras (contextual info captured during chat) are merged into FormDto.extras.

dtoField Auto-Derive​

When a field definition omits dtoField, the field name is used automatically:

# These are equivalent:
email:
type: email
dtoField: email # explicit

email:
type: email
# dtoField auto-derives to "email"
tip

You only need explicit dtoField when the form field name differs from the desired DTO key. Most fields can omit it.

Explicit dtoField Mapping​

Use explicit dtoField when the form field name should map to a different DTO key:

# Form field "preferredDate" maps to DTO key "date"
preferredDate:
type: date
dtoField: date
promptKey: fields.date.ask
labelKey: fields.date.label
descriptionKey: fields.date.description

Common use case: multiple form fields across branches that represent the same concept:

# All three map to the same "timeslot" DTO field
preferredTimeCampus:
type: smartSelect
dtoField: timeslot

preferredTimeMoveout:
type: select
dtoField: timeslot

preferredTimeFlexible:
type: time
dtoField: timeslot

This is valid because these fields are in different branches — only one is active at a time.

Multiple Fields, Same dtoField​

Fields in different branches can legitimately share a dtoField:

# OK: preferredDate exists in both branches, but only one branch is active
formFlow:
- serviceType:
homeCleaning:
- preferredDate: {} # dtoField: date
windowCleaning:
- preferredDate: {} # dtoField: date (same — fine, different branch)

However, fields on the same path must NOT share a dtoField (for main DTO fields). The system validates this at config load time:

# ERROR: fieldA and fieldB are sequential (same path), both map to "sharedName"
formFlow:
- fieldA: {}
- fieldB: {}

fieldDefinitions:
fieldA:
type: text
dtoField: sharedName
fieldB:
type: text
dtoField: sharedName

Error at startup:

Fields 'fieldA' and 'fieldB' both map to dtoField 'sharedName' on the same form path

Extras​

Fields whose effective dtoField is NOT in the defaults.dtoFields list are routed to FormDto.extras. They are keyed by field name (not dtoField value) to prevent overwrites.

defaults:
dtoFields: [serviceType, date, name, email] # main DTO fields
persistOthersTo: extras

fieldDefinitions:
windowCount: # not in dtoFields → extras
type: number
windowType: # not in dtoFields → extras
type: select

Result in the DTO:

{
"fields": {
"serviceType": "windowCleaning",
"date": "2026-04-05",
"name": "John",
"email": "john@example.com"
},
"extras": {
"windowCount": 12,
"windowType": "outsideOnly"
}
}

Conversation Extras​

During the chat, the LLM can capture contextual information that the user mentions but isn't covered by any form field. These are stored separately via the addExtraInfo tool:

  • petInfo: "I have a friendly dog at home"
  • keysLocation: "Keys are with the neighbor in unit 4B"
  • customerAvailabilityNote: "Won't be home before 10am"

These are merged into FormDto.extras alongside answer-based extras:

{
"fields": { "name": "John", "email": "john@example.com" },
"extras": {
"windowCount": 12,
"petInfo": "I have a friendly dog at home",
"keysLocation": "Keys are with the neighbor in unit 4B"
}
}

Complete DTO Example​

For a window cleaning booking:

{
"sessionId": "a1b2c3d4-e5f6-...",
"customerId": "SE00123",
"username": "user@example.com",
"configId": "customers/SE00123",
"configVersion": "abc123",
"submittedAt": "2026-03-23T19:05:33",
"fields": {
"serviceType": "windowCleaning",
"date": "2026-04-05",
"timeslot": "09:00",
"name": "Prasannjeet Singh",
"email": "prasannjeet@duck.com",
"phone": "0700617660",
"personalNumber": "199110107673",
"address": "Lyan 62, LGH 1008",
"postNumber": "35252",
"city": "Vaxjo"
},
"extras": {
"windowType": "outsideOnly",
"windowCount": 12,
"petInfo": "I have a dog at home."
}
}