Skip to content

Martini JSON Functions

Overview

Martini's built-in JSON functions let you convert, read, and write JSON data directly inside your services and workflows—without external libraries or boilerplate code. Use these functions to transform data models to JSON strings, parse JSON from files and streams, and stream large JSON datasets one record at a time to keep memory usage low.

What You Will Learn

  • How to convert models to/from JSON
  • How to parse JSON from streams and files
  • How to write and format JSON output
  • How to process large JSON with streaming cursors
  • How to build JSON incrementally and append entries

When To Use This

Use Martini JSON functions when you need to:

  • Convert data models to JSON for API responses, file exports, or message publishing
  • Parse incoming JSON payloads from HTTP requests, message queues, or file imports
  • Process large JSON files that cannot fit comfortably in memory using cursors
  • Write structured JSON output incrementally during a service or workflow execution

Prerequisites

JSON Conversion Functions

Martini provides a set of functions for converting between data models and JSON representations. These cover the most common data exchange scenarios: serializing models to strings, deserializing strings and streams back to models, and writing JSON to Java output targets.

Getting Started with JSON Data Model Conversion

The quickest way to produce a JSON string from a data model is to use modelPropertyToJsonString. Given a data model with populated properties, the function serializes every property into a valid JSON string you can use immediately.

Example — Converting a data model to a JSON string:

Suppose you have a data model myDataModel with these properties set at runtime:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
myDataModel {
    status = success
    data {
        id = 101
        title = Sample Product
        price = 19.99
        inStock = true
    }
    message = Product fetched successfully
}

Call the function with modelProperty set to myDataModel:

1
modelPropertyToJsonString(modelProperty: myDataModel)

Expected result: The function returns the following JSON string:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "status": "success",
  "data": {
    "id": 101,
    "title": "Sample Product",
    "price": 19.99,
    "inStock": true
  },
  "message": "Product fetched successfully"
}

Example — Parsing a JSON string back into a data model:

To reverse the operation, use jsonStringToModelProperty and then map its return value to a typed data model:

1
2
jsonStringToModelProperty(jsonString: myJsonString)
// Map the returned object to a typed data model whose property names match the JSON keys

The jsonStringToModelProperty function returns a wrapped object rather than a typed data model. Map the returned object to a target data model whose property names match the JSON keys to access the parsed values.

Expected result: After mapping, the target data model contains the properties from the JSON string with their values correctly populated.

JSON Conversion Function Reference

The following table lists the available JSON conversion functions and their primary uses:

Function Primary Use
modelPropertyToJsonString(...) Convert a data model into a JSON string
jsonStringToModelProperty(...) Parse a JSON string into a wrapped data model object
inputStreamToModelProperty(...) Convert JSON content from an InputStream into a wrapped data model object
readerToModelProperty(...) Convert JSON content from a Reader into a wrapped data model object
writeJson(...) Stream JSON output directly to a Writer
printJson(...) Generate formatted, human-readable JSON output using an IndentPrinter

How JSON Type Mapping Works

Understanding how Martini maps types between data models and JSON helps you predict and control the output of conversion functions in both directions.

Data model to JSON — type mapping rules:

Each data model property's value type determines the corresponding JSON value type:

Data model property type JSON value type
String JSON string ("value")
Whole number (Integer, Long) JSON number (101)
Decimal number (Float, Double, BigDecimal) JSON number (19.99)
Boolean JSON boolean (true / false)
Nested data model JSON object ({ ... })
Array property JSON array ([ ... ])

Null properties are excluded by default

When serializing a data model to JSON, properties with null values are omitted unless the includeNulls parameter is set to true or the property is marked as Required. Required properties are always serialized, even when their value is null. For details on configuring the Required meta-property, see Accessing Individual Property Configuration.

JSON to data model — type mapping rules:

When converting JSON back to a data model, the resulting property types are inferred from the JSON value:

JSON value type Data model property type
JSON string String
JSON number (integer or decimal) BigDecimal
JSON boolean Boolean
JSON object Nested data model
JSON array Array property; element type inferred from the first element in the array
JSON null field Property of type Object with a null value

JSON Conversion Benefits and Use Cases

JSON conversion functions eliminate manual parsing and serialization code, reducing development time and the risk of producing malformed output. Common real-world applications include:

  • API integration: Parse an incoming JSON request body from an InputStream using inputStreamToModelProperty, process it, then serialize the result with modelPropertyToJsonString for the API response.
  • File export: Populate a data model with records and write a formatted JSON file using printJson with an IndentPrinter.
  • Message processing: Deserialize JSON messages from a queue into typed data models for safe downstream processing.

Troubleshooting JSON Conversion Issues

Problem Detection Cause Fix
Some properties are missing from the generated JSON output Expected fields do not appear in the JSON output Properties with null values are excluded by default during serialization Set includeNulls to true to serialize all null properties. To serialize only specific null properties, mark them as Required in the data model.
Array element types are incorrect after deserialization Array items are treated as unexpected types Array element type inference is based on the first array element Ensure arrays contain consistent element types before conversion
JSON parsing fails during conversion Conversion function throws a parse or syntax exception JSON input is malformed or contains invalid syntax Validate the JSON structure and confirm the payload is valid JSON before conversion

JSON Streaming Cursor Functions

Martini's JSON cursor functions let you read and write large JSON datasets one record at a time, avoiding the memory overhead of loading an entire file or stream into memory at once. For a general introduction to cursors in Martini, see Martini Cursors.

Getting Started with JSON Input Cursors

Use openJsonFileInputCursor to iterate over specific nodes in a JSON file without loading the full file into memory.

Example workflow structure using openJsonFileInputCursor:

flowchart LR
    s[...]
    a["`**Function Node:** Open JSON file input cursor and map to _myCursor_`"]
    b["`**Repeat Node:** Iterate over _myCursor_`"]
    c["`Process the current _myCursor_ entry`"]
    d[...]
    s --> a --> b -- each --> c
    b -- then --> d

Map the input cursor to an array property (here, myCursor), then use a Repeat Node with myCursor as its input array. Each iteration yields one matched JSON node.

Expected result: Each iteration provides a nodeName identifying which JSON field was matched and a cursorRecord containing the parsed value for that node.

How JSON Input Cursor Streaming Works

Martini provides two functions for opening JSON input cursors:

  • openJsonFileInputCursor(...) — Opens a cursor on a JSON file at a specified file path.
  • openJsonStreamingInputCursor(...) — Opens a cursor on a JSON InputStream.

Both functions accept a jsonNodeNames parameter—a list of JSON field names to seek while scanning the data. The cursor scans the JSON sequentially and yields only records matching those names.

How each cursor iteration works:

Each iteration of the cursor returns a data model entry with two properties:

  • nodeName — The matched JSON field name from jsonNodeNames
  • cursorRecord — The content of that node (a scalar value or a data model)

Example — Iterating over selected nodes:

Given this JSON input:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "status": "success",
  "data": {
    "id": 101,
    "title": "Sample Product",
    "price": 19.99,
    "inStock": true
  },
  "message": "Product fetched successfully"
}

With jsonNodeNames set to ["status", "data"]:

  • Iteration 1: nodeName = "status", cursorRecord = "success"
  • Iteration 2: nodeName = "data", cursorRecord contains a data model with properties id, title, price, and inStock
  • The cursor ends—"message" is not in jsonNodeNames and is skipped.

Targeting a root-level JSON array:

If the JSON root is an array, add the special value $jsonRoot to jsonNodeNames:

1
openJsonFileInputCursor(filePath: '/data/items.json', jsonNodeNames: ['$jsonRoot'])

Each cursor record corresponds to one entry in the root array. For example, if the file contains an array of three objects, the cursor yields three iterations—one cursorRecord per array entry.

Targeting nested fields directly:

Node names can reference nested fields by name. For example, specifying "inStock" instead of "data" yields:

  • nodeName = "inStock", cursorRecord = true

Parent and Child Node Matching

If jsonNodeNames includes both a parent node (for example, "data") and a child of that node (for example, "inStock"), the cursor returns the parent's record and does not separately yield the child. To retrieve only the child, remove the parent name from jsonNodeNames.

How JSON Output Cursors Work

The openJsonOutputCursor(...) function opens a write cursor that incrementally builds a JSON document and writes it to a configured destination, including (but not limited to) a StringBuffer or OutputStream.

Once the cursor is open, append data models to it. Each appended model contributes its properties as JSON fields or array entries in the final output.

Example workflow structure — Writing a JSON array:

flowchart LR
    s[...]
    a["`Prepare _products_ data model array`"]
    b["`**Function Node:** Open JSON output cursor and map to _myOutputCursor_`"]
    c["`**Repeat Node:** Iterate over _products_ with _myOutputCursor_ as the output array`"]
    d["`**Map Step**: Map the current _products_ to the current _myOutputCursor_`"]
    e[...]
    s --> a --> b --> c -- each --> d
    c -- then --> e

Assuming you are writing to /output/products.json, start with a products data model array already populated with entries you want to write—in this case two entries:

  • id: 1, title: "Sample Product 1"
  • id: 2, title: "Sample Product 2"

The output cursor returned by the function is then mapped to myOutputCursor.

In the Repeat Node, products is set as the input array and myOutputCursor as the output array. For each iteration, the current products entry is mapped to the current myOutputCursor—this is what appends the entry to the cursor and writes it to the destination as a JSON array entry.

Expected result: /output/products.json contains a JSON array with two entries:

1
2
3
4
[
  {"id": 1, "title": "Sample Product 1"},
  {"id": 2, "title": "Sample Product 2"}
]

Writing individual JSON fields (non-array output):

Output cursors are not limited to arrays. To write individual fields, append a data model with the desired properties directly to the cursor—without an Repeat Node or an Iterate Step. For example, appending a model with status = "success" produces "status": "success" in the JSON output.

To append data models to a cursor outside of an Repeat node or Iterate step, see Service Functions for cursor interaction functions.

JSON Streaming Benefits and Use Cases

Cursor-based streaming is the right choice when working with JSON data at scale. Instead of loading an entire dataset into memory, your service processes one record at a time—keeping memory usage constant regardless of file size.

Real-world applications include:

  • Large file processing: Stream a multi-gigabyte product catalog JSON file and transform each product record individually.
  • ETL pipelines: Read from a JSON source stream, apply transformations per record, and write to a JSON output file—all within a single service.
  • Selective extraction: Extract only specific named nodes from a complex JSON document without parsing the entire structure.

Troubleshooting JSON Cursor Issues

Problem Detection Cause Fix
Fewer cursor records than expected Iteration ends before all matching nodes are processed A specified node name is a child of another specified node name; the cursor returns the parent's record instead of separately yielding the child Remove the parent node name from jsonNodeNames and keep only the child, or vice versa
Root-level array yields no records Cursor returns zero iterations on a JSON file whose root is an array The root array has no field name and $jsonRoot was not included in jsonNodeNames Add "$jsonRoot" to jsonNodeNames to target the unnamed root array
Output JSON is invalid after writing The final JSON file fails validation or cannot be parsed Print configuration causes { and } to wrap what should be a root-level JSON array Review and correct the print configuration on openJsonOutputCursor to match the intended JSON structure

Helpful Resources