Skip to content

Writing JSON Documents

This article writes a JSON document to a file. It is the same problem the Xml Writer page solves with XPaths — a flat dataset that has to come out nested — solved with JPaths instead.

Writing JSON to a service is a different article

IMan writes JSON through the same Writer whether the document goes to a file or to a URL, but everything interesting about a service is in the request rather than in the document: two URLs, an operation each, and a field that chooses between them per record.

This page is the file case: one document, no request, and the whole dataset in it.

The source data

sample-data/csv/xero-contacts.csv, six contacts, one line each:

Name,FirstName,LastName,EmailAddress,AccountNumber,AddressLine1,City,Region,
PostalCode,Country,PhoneAreaCode,PhoneNumber,IsCustomer,IsSupplier
Kestrel Coffee Roasters,Anna,Petrov,[email protected],KCR-001,
14 Mill Lane,Bristol,Avon,BS1 4TR,United Kingdom,0117,496 0181,TRUE,FALSE

Flat, with the address and phone columns sitting alongside the rest. The document it has to become is not flat:

{
  "Contacts": [
    {
      "Name": "Kestrel Coffee Roasters",
      "FirstName": "Anna",
      "LastName": "Petrov",
      "EmailAddress": "[email protected]",
      "AccountNumber": "KCR-001",
      "Addresses": [
        { "AddressLine1": "14 Mill Lane", "City": "Bristol", "Region": "Avon",
          "PostalCode": "BS1 4TR", "Country": "United Kingdom" }
      ],
      "Phones": [
        { "PhoneAreaCode": "0117", "PhoneNumber": "496 0181" }
      ],
      "IsCustomer": true,
      "IsSupplier": false
    },
    …
  ]
}

Two levels of nesting and two arrays, out of one flat row. No transform builds that structure — the field paths do.

Step a - the Reader

A CSV Reader over the file, with Header Rows set to 1, Mapping Style By Field Heading, and its transaction renamed Contact.

The CSV Reader Field Mapping tab. Fifteen fields are listed with their types; IsCustomer and IsSupplier are Boolean and every other field is Text.

Set IsCustomer and IsSupplier to Boolean here. This is the only thing on the reader that matters to the document: JSON distinguishes true from "TRUE", and the Writer takes that distinction from the dataset's field types rather than from anything on the Writer itself. Left as Text, the two values come out as quoted strings and a strict consumer rejects them.

The same is true of numbers. A Decimal field writes 282.00; the same value in a Text field writes "282.00".

Step b - the target

The JSON Writer Setup tab, Target section. Target is File, File System Windows, File Path C colon backslash IMan backslash OutputData backslash Docs, File Name contacts.json and Encoding Method Unicode UTF-8.

Field Value
Target File Changing this resets the section below it and asks first
File System Windows
File Path C:\IMan\OutputData\Docs
File Name contacts.json
Encoding Method Unicode (UTF-8)
Overwrite Existing File ✔

Switching Target from the default http(s) Url to File raises "Changing the target will reset its settings." Take it at its word: it discards the Insert Url, the operations and the Modify Field, so set the target before configuring anything beneath it.

One document, or one per contact

The JSON Writer Setup tab, Commit section. Create File When No Data is unticked, Generate File Per Transaction is set to none, and Batch Size is 0.

Generate File Per Transaction left at (none) builds one document from the whole dataset — six contacts in one array, which this page wants.

Name a transaction there instead and the Writer commits once per record: six documents. Against a file target that means six writes, and with a static file name each overwrites the last, leaving one file holding the final contact. If you want a file per record, put a wildcard in the file name or tick Evaluate FileName and build one.

That setting is doing something quite different on the webservice writer, where one document per record makes a per-record URL possible.

Step c - building the document

The JSON Writer Field Mapping tab. Initial JPath is Contacts with square brackets, and the field grid maps Name, FirstName, LastName, EmailAddress and AccountNumber to their own names, the five address fields under Addresses square brackets, the two phone fields under Phones square brackets, and IsCustomer and IsSupplier to their own names.

The Initial JPath

Initial JPath is the wrapper the whole document is built inside:

Contacts[]

The trailing [] makes it an array, and the transaction's records become objects in it. Six records, six objects.

Transaction Type JPath is left empty for the top transaction — it is already positioned by the Initial JPath. A child transaction would carry its own path here.

The field paths

Field JPath
Name Name
FirstName FirstName
LastName LastName
EmailAddress EmailAddress
AccountNumber AccountNumber
AddressLine1 Addresses[]/AddressLine1
City Addresses[]/City
Region Addresses[]/Region
PostalCode Addresses[]/PostalCode
Country Addresses[]/Country
PhoneAreaCode Phones[]/PhoneAreaCode
PhoneNumber Phones[]/PhoneNumber
IsCustomer IsCustomer
IsSupplier IsSupplier
SYS.INPUTFILE (not exported)

Three rules, all of which catch people:

  • Segments are separated by a forward slash, not a dot. Addresses[]/City, never Addresses[].City. JPath here is a path, and it reads like an XPath rather than like JavaScript.
  • [] on an intermediate segment is the whole trick. An array indicator part-way along a path creates the array and puts the rest of the path inside its first object — so five fields that all begin Addresses[]/ merge into one object in one array. Flat data goes in, a nested document comes out, and no Hierarchy transform was needed.
  • A field with no JPath and Export ticked writes nothing useful. Untick Export on anything you do not want in the document; SYS.INPUTFILE is the usual candidate.

The field dialog

The JSON Writer's Field Mapping dialog for AddressLine1. Field Name is AddressLine1, Export Field and Is Relative JPath are ticked, JPath is Addresses square brackets slash AddressLine1, and Default Value is empty with its help text below.

Default Value decides what happens when a field is empty, and the three cases are genuinely different: left empty the property is not written at all, a value writes that value, and the literal null writes a JSON null. A service that distinguishes "absent" from "explicitly cleared" cares about the difference.

Is Relative JPath is ticked, so the path is appended to the transaction's. See the warning below before clearing it.

Step d - run it

Refresh from the reader down, then read the file:

{
  "Contacts": [
    {
      "Name": "Halden Timber Supplies",
      "FirstName": "Owen",
      "LastName": "Blake",
      "EmailAddress": "[email protected]",
      "AccountNumber": "HTS-002",
      "Addresses": [
        {
          "AddressLine1": "Unit 7 Northgate Estate",
          "City": "Leeds",
          "Region": "West Yorkshire",
          "PostalCode": "LS2 8JS",
          "Country": "United Kingdom"
        }
      ],
      "Phones": [
        { "PhoneAreaCode": "0113", "PhoneNumber": "244 7712" }
      ],
      "IsCustomer": false,
      "IsSupplier": true
    }
  ]
}

IsCustomer and IsSupplier are unquoted false and true, from the two Boolean columns set on the reader. The output is indented, so it is readable as it stands.

When it does not work

  • The values are quoted when they should not be. The field is Text in the dataset. Fix it on the reader, not on the writer.
  • The nested object did not appear. A dot instead of a slash — Addresses[].City writes a property whose name contains a dot.
  • An array arrives with one long string in it. An array-valued field whose value has an unterminated double quote in it: the whole value becomes the single element. See the JSON writer's array fields.
  • Only the last contact is in the file. Generate File Per Transaction names a transaction, so each record wrote its own document over the last.

Verified against IMan 6.1 on 3 September 2026, against the DOCSWRITE integration described in sample-data/README.md.