Email this post as a PDFDevelopment · 1 October 2026 · 2 min read

JSON in AL: JsonObject, JsonToken, JsonArray and the patterns that do not break

Every integration speaks JSON and AL has a decent set of types for it. Building a payload, parsing a response, handling missing and null properties without an error, nested objects and arrays, and the helper the base application now ships so you stop writing the same token-to-text code.

Contents
  1. Building
  2. Parsing safely
  3. Arrays
  4. The helper you should use
  5. Big payloads
  6. Errors that recur

AL's JSON types (JsonObject, JsonArray, JsonToken, JsonValue) are reference types with a small, consistent API. Most JSON bugs in extensions come from two habits: assuming a property exists, and converting through Format. These are the patterns that avoid both.

Building

procedure BuildShipment(Shipment: Record "CON Courier Shipment") Body: JsonObject
var
    Addr: JsonObject;
    Parcels: JsonArray;
    Parcel: JsonObject;
begin
    Body.Add('reference', Shipment."No.");
    Body.Add('shipDate', Format(Shipment."Shipment Date", 0, 9));  // ISO 8601
    Addr.Add('line1', Shipment."Ship-to Address");
    Addr.Add('postcode', Shipment."Ship-to Post Code");
    Body.Add('address', Addr);
    Parcel.Add('weightKg', Shipment."Weight (kg)");
    Parcels.Add(Parcel);
    Body.Add('parcels', Parcels);
end;

Dates and times with Format(x, 0, 9) (the XML/ISO format), decimals as decimals (the type handles it; never Format a decimal into JSON, it will use the user's locale and produce a comma). Property names are technical strings, so they are locked labels in a real codebase; shown inline here for brevity.

Parsing safely

procedure ReadTrackingNo(ResponseText: Text) TrackingNo: Text
var
    Root: JsonObject;
    Tok: JsonToken;
begin
    if not Root.ReadFrom(ResponseText) then
        Error(InvalidJsonErr);
    if not Root.Get('trackingNumber', Tok) then
        exit('');                                  // property missing: decide, don't crash
    if Tok.AsValue().IsNull() then
        exit('');
    TrackingNo := Tok.AsValue().AsText();
end;

Always Get (returns false if missing) rather than indexing; check IsNull before AsText; use AsValue().AsDecimal(), AsBoolean(), AsDate() for typed reads. For nested paths, Root.SelectToken('address.postcode', Tok) accepts a JSONPath-style expression and is clearer than chained Gets.

Arrays

if Root.Get('parcels', Tok) and Tok.IsArray() then
    foreach Item in Tok.AsArray() do
        ProcessParcel(Item.AsObject());

The helper you should use

The base application ships Codeunit "JSON Management" (older) and, in the System Application, helpers for common conversions; most teams keep a small "CON Json Helper" with GetText(Obj, Path, Default), GetDecimal, GetDate that wrap the null and missing checks once. Write it once, test it once, and every integration reads the same way.

Big payloads

ReadFrom(InStream) and WriteTo(OutStream) avoid building a huge Text. For large arrays, process as you iterate rather than copying into temporary records first, unless you need to sort.

Errors that recur

  • Comparing Tok.AsValue().AsText() to a number: compare typed.
  • Adding a key twice to a JsonObject: it errors; use Replace if it might exist.
  • Treating an empty string and null as the same: APIs do not.
  • Logging the whole payload to telemetry with personal data in it. Log the IDs.
Email this post as a PDF
A branded PDF of this post, sent to your inbox to keep, print or share. Copy in colleagues if you like.
We only use these addresses to send this PDF. Nobody is added to a mailing list. Mike at Business Boxed receives a copy. See our privacy notice.
Email this post
Talk to us
Want this done in your Business Central?
Book a 30 minute callGet the monthly newsletter

Related posts