> ## Documentation Index
> Fetch the complete documentation index at: https://grandcentral.backbase.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Transform and validate data

> JSON Schema validation, JOLT and XSLT transformations, dynamic field translation, and helper bean calls

Every operation validates the inbound request against a JSON Schema, then transforms the canonical Grand Central payload into the vendor's shape and the vendor's response back into the Grand Central shape. Use JOLT for JSON, XSLT for XML, and lift value-to-value substitutions out of the transformation files into properties.

## Validate the request schema

Every request-bearing operation gets a JSON schema in `src/main/resources/<operation>-schema-validation.json`, applied at the very start of the route:

```java theme={"system"}
.to("json-validator:{{?resourcesBasePath}}create-entity-schema-validation.json")
```

If validation fails, `kamelet:json-validation-exception-handler` catches the resulting `JsonValidationException` and returns a Grand Central-shaped error response.

### Convert an OpenAPI spec to JSON Schema

To generate a JSON Schema file from an OpenAPI spec, follow these steps:

1. Install the conversion tool:
   ```shell theme={"system"}
   npm install -g yaml-to-json-schema
   ```
2. Run the following command to generate a JSON Schema for all schemas in the OpenAPI spec:
   ```shell theme={"system"}
   npx yaml-to-json-schema {path-to-openapi-spec-doc}.yaml > {json-schema-file-name}.json
   ```
3. In the generated schema file, prefix a `/` at the beginning of all reference paths in `$ref` values.

   **Example:** Change `"$ref": "#definitions/Address"` to `"$ref": "#/definitions/Address"`.

A typical Draft-07 schema skeleton looks like this:

```json theme={"system"}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "CreateEntity",
  "type": "object",
  "required": ["requiredField"],
  "properties": {
    "requiredField": { "type": "string", "maxLength": 255 },
    "enumField":     { "enum": ["VALUE_A", "VALUE_B"] },
    "numericField":  { "type": "number" }
  }
}
```

## JOLT for REST/JSON connectors

JOLT is a JSON-to-JSON declarative transformation language. Use it to map the Grand Central canonical request and response shape to the vendor shape and back.

The most common route call takes a JSON String in and out:

```java theme={"system"}
.to("jolt:{{?resourcesBasePath}}create-entity-request-transformation.json"
    + "?transformDsl=Chainr&inputType=JsonString&outputType=JsonString")
```

When the body is already a Java Map (for example, mid-Splitter), use Hydrated input:

```java theme={"system"}
.to("jolt:{{?resourcesBasePath}}secondary-item-transformation.json"
    + "?transformDsl=Chainr&inputType=Hydrated&outputType=JsonString")
```

Chainr spec structure:

```json theme={"system"}
[
  { "operation": "shift",   "spec": { /* ... */ } },
  { "operation": "default", "spec": { /* ... */ } },
  { "operation": "remove",  "spec": { /* ... */ } }
]
```

## XSLT for SOAP/XML connectors

XSLT 3.0 produces the SOAP envelope on the request side and reshapes the SOAP response into the Grand Central canonical JSON-friendly XML on the response side.

Route call:

```java theme={"system"}
.to("xslt:{{?resourcesBasePath}}get-entity-request-transformation.xslt")

// Dynamic XSLT path: use toD when the file name is computed at runtime
.toD("xslt:{{?resourcesBasePath}}${exchangeProperty.xsltFile}-transformation.xslt")
```

When the XSLT builds the full request from headers and properties without JSON input:

```java theme={"system"}
.setBody(simple(EMPTY_ROOT_XML_TAG))    // "<root></root>"
.to("xslt:{{?resourcesBasePath}}request.xslt")
```

Access Camel headers from inside an XSLT by declaring them as parameters; Camel injects matching header values:

```xml theme={"system"}
<xsl:param name="entityId"/>
<xsl:param name="currentDateTime"/>
```

XSLT tips:

* Always declare the vendor namespace at the top (`xmlns:ns="..."`). XPath without the prefix matches nothing.
* Use `omit-xml-declaration="yes"` on the root `xsl:output` so the route can pipe the XML cleanly into `direct:xmlToJson`.
* To force a JSON array on a single element, emit a paired empty-and-populated element (for example, `<amounts/><amounts>...</amounts>`). The `RemoveNullOrEmptyJsonSerializer` configured on the JSON marshaller drops the empty placeholder, leaving an array.
* Wrap optional sections in `xsl:if` to avoid emitting empty objects.

## Dynamic field translation through properties

For straight value-to-value substitutions (for example, mapping a vendor code like `SAV` to a Grand Central value like `savings-account`), avoid embedding if/else logic inside JOLT or XSLT. The SDK ships two helpers that pull the mappings straight from `application.properties`. The helpers keep transformation files focused on structure, remove a major source of duplication across operations, and make every value mapping visible in one place.

Pick the helper bean call that matches the body type:

```java theme={"system"}
// JSON bodies
.bean(GrandCentralUtil.class, "replaceValuesForJson(${exchange})")

// XML bodies
.bean(GrandCentralUtil.class, "replaceValuesForXML(${exchange})")
```

Both helpers read the `fieldsToBeReplaced` property and its related entries from the connector's properties, then apply the substitutions in place.

Simple mapping uses one field per row with comma-separated field names:

```properties theme={"system"}
fieldsToBeReplaced=MajorAccountTypeCode,MinorAccountTypeCode
MajorAccountTypeCode.SAV=kind4
MinorAccountTypeCode.SAV=savings-account
```

Composite mapping handles cases where a unique target value depends on a combination of fields. Use `@` as the delimiter:

```properties theme={"system"}
fieldsToBeReplaced=MajorAccountTypeCode@MinorAccountTypeCode
MajorAccountTypeCode.SAV@MinorAccountTypeCode.SAV=kind3@savings-account
```

Override the XML namespace when the field lives outside the default namespace:

```properties theme={"system"}
namespaceURI.RtxnTypeCode=http://schemas.example.com/types
RtxnTypeCode.DEP=DEPOSIT
RtxnTypeCode.WTH=WITHDRAW
```

Shared lookups across operations handle cases where different operations expose the same logical field under different element names. Redirect them to a common lookup that defines the mapping table once:

```properties theme={"system"}
mappingField.MajorAccountType=MajorAccountTypeCode
```

Minimum BOM versions for this feature:

| Camel-K runtime | Minimum `grandcentral-bom` version |
| --------------- | ---------------------------------- |
| Pre-2.6         | 2.2.119                            |
| 2.6             | 2.3.17                             |

## JSON and XML marshaling for SOAP

Define two helper routes once and reuse them across every SOAP operation:

```java theme={"system"}
JacksonDataFormat jacksonDataFormat = new JacksonDataFormat();
jacksonDataFormat.setObjectMapper(new ObjectMapper().registerModule(
    new SimpleModule().addSerializer(Object.class, new RemoveNullOrEmptyJsonSerializer())));

from("direct:xmlToJson").unmarshal().jacksonXml().marshal(jacksonDataFormat);
from("direct:jsonToXml").unmarshal().json().marshal().jacksonXml();
```

`RemoveNullOrEmptyJsonSerializer` strips nulls and empty strings or objects on serialization. The serializer makes the array trick in XSLT work and keeps response payloads tidy.

After every `direct:xmlToJson`, run the type-coercion processor to fix string-to-number/boolean coercion from Jackson's XML deserializer:

```java theme={"system"}
.process(GrandCentralUtil::correctNumericAndBooleanValuesInJsonFromProperties)
```

It uses the comma-separated `typeConversion.skipAttributes` property to decide which fields stay strings, typically identifiers.

## Input validation with PredicatesValidatingProcessor

Use this for header or format validation that should produce a Grand Central error response on failure.

```java theme={"system"}
.process(new PredicatesValidatingProcessor(Map.ofEntries(
    Map.entry(
        header("entityId").regex("^[A-Z0-9]{8,16}$"),
        new ErrorMessage("entityId: must be 8-16 uppercase alphanumeric characters")
    ),
    Map.entry(
        header("entityType").in("TYPE_A", "TYPE_B"),
        new ErrorMessage("entityType must be TYPE_A or TYPE_B")
    )
)))
```

## Common GrandCentralUtil inline helpers

A handful of `bean(...)` calls come up over and over:

```java theme={"system"}
.bean(GrandCentralUtil.class,
    "appendAttribute(${body}, attributeName, ${header.someHeader})")

.bean(GrandCentralUtil.class,
    "appendJsonNode(${body}, 'status', '\"OK\"')")

.bean(GrandCentralUtil.class,
    "appendAttributeToNewObject(${body}, attributeName, ${header.value})")

.bean(GrandCentralUtil.class,
    "appendMultipleAttributes(${body}, ${exchangeProperty.attrMap})")

.bean(GrandCentralUtil.class,
    "convertHeadersToJsonBody(${exchange},'header1:header2:header3')")

// SOAP only: coerce numeric/boolean values after xmlToJson
.process(GrandCentralUtil::correctNumericAndBooleanValuesInJsonFromProperties)

// SOAP only: append XML attributes from headers into a DOM
.bean(GrandCentralUtil.class,
    "appendXMLAttributesFromHeader(${exchange},'header1:header2','parentTagName')")
```

## Next step

For the Camel and HTTP properties that drive these transformations at runtime, see [Configure properties](/platform/developer-guides/build/configure-properties).
