In this video, we’re building a generic AL function that takes a Record variable and producing a JSON Object, check it out:

In this video, Erik walks through building a generic Record to JSON function in AL for Business Central. The function accepts any record (via a Variant parameter), iterates over all its fields using RecordRef and FieldRef, and produces a JsonObject. Along the way, Erik covers handling different field types, calculating FlowFields, filtering out FlowFilters, sanitizing field names for valid JSON keys, and using AL’s format number 9 (XML format) for consistent data formatting.
Setting Up the Code Unit
Erik starts by creating a new code unit called Json Tools. The core idea is to have a procedure that takes a Variant parameter (which allows passing any record type) and returns a JsonObject. This builds on the concept from a previous video about using Variants to get to RecordRef.
codeunit 50100 "Json Tools"
{
procedure RecToJson(Rec: Variant): JsonObject
var
Ref: RecordRef;
FRef: FieldRef;
JObj: JsonObject;
i: Integer;
begin
if not Rec.IsRecord then
Error('Parameter Rec is not a record');
Ref.GetTable(Rec);
for i := 1 to Ref.FieldCount do begin
FRef := Ref.FieldIndex(i);
case FRef.Class of
FieldClass::Normal:
JObj.Add(GetJsonFieldName(FRef), FieldRefToJsonValue(FRef));
FieldClass::FlowField:
begin
FRef.CalcField();
JObj.Add(GetJsonFieldName(FRef), FieldRefToJsonValue(FRef));
end;
FieldClass::FlowFilter:
; // Skip flow filters entirely
end;
end;
exit(JObj);
end;
Converting a Variant to a RecordRef
The first step inside the function is to validate that the passed Variant is actually a record. If it is, we call Ref.GetTable(Rec) to get a RecordRef from the variant. This gives us access to field-level iteration without knowing the specific table at compile time.
Iterating Over Fields
Using Ref.FieldCount and Ref.FieldIndex(i), we loop through every field on the record. The FieldIndex method is key here — it gives us the field by its ordinal position rather than its field number, making sequential iteration possible.
Handling Field Classes: Normal, FlowField, and FlowFilter
Not all fields are equal. Erik introduces a case statement on FRef.Class to handle the three field classes differently:
- Normal fields — added directly to the JSON object
- FlowFields — must be calculated first with
FRef.CalcField()before reading their value - FlowFilters — skipped entirely since they aren’t real data fields
Erik discovers an interesting behavior during testing: when using Rec directly from a page (like the Customer Card), some FlowFields appear to be auto-calculated. However, when loading a record via code with Find('-'), the FlowFields return zero unless explicitly calculated. This confirms the importance of calling CalcField().
Converting FieldRef Values to JSON Values
The trickiest part of this function is converting FieldRef.Value (which returns a Joker type — essentially an untyped value) into a properly typed JsonValue. You cannot implicitly convert a Joker to a JsonValue, so Erik uses a case statement on the field type to perform explicit type conversions:
local procedure FieldRefToJsonValue(FRef: FieldRef): JsonValue
var
V: JsonValue;
D: Date;
T: Time;
DT: DateTime;
begin
case FRef.Type of
FieldType::Date:
begin
D := FRef.Value;
V.SetValue(D);
end;
FieldType::Time:
begin
T := FRef.Value;
V.SetValue(T);
end;
FieldType::DateTime:
begin
DT := FRef.Value;
V.SetValue(DT);
end;
else
V.SetValue(Format(FRef.Value, 0, 9));
end;
exit(V);
end;
The key insight here is that JsonValue.SetValue() has 12 overloads covering the most common data types. By assigning FRef.Value to a typed local variable first (like D: Date), AL can resolve the correct overload.
Format Number 9: XML/Raw Format
For the else case, Erik uses Format(FRef.Value, 0, 9). The format number 9 in AL produces the XML/raw data format, which aligns well with JSON’s expected data representations. This avoids locale-specific formatting issues and gives consistent, machine-readable output for field types that don’t need special handling.
Sanitizing Field Names for JSON Keys
Field names in Business Central can contain characters that are problematic as JSON keys (like periods, slashes, parentheses, etc.). Erik creates a helper function to clean these up:
local procedure GetJsonFieldName(FRef: FieldRef): Text
var
Name: Text;
i: Integer;
begin
Name := FRef.Name;
for i := 1 to StrLen(Name) do
if Name[i] < '0' then
Name[i] := '_';
exit(
Name
.Replace('__', '_')
.TrimEnd('_')
.TrimStart('_')
);
end;
}
The approach replaces any character with an ASCII value less than '0' (which is 48) with an underscore. This filters out most special characters while preserving letters and numbers. The function then cleans up double underscores and trims leading/trailing underscores for cleaner JSON keys.
Erik initially tried using 'A' (65) as the threshold, which caused duplicate key errors because numbers in field names like "Global Dimension 1 Code" and "Global Dimension 2 Code" would both become the same key. Switching to '0' (48) preserves digits and fixes the collision.
Testing the Function
To test, Erik creates a simple page action that loads a Customer record and calls the function:
var
JObj: JsonObject;
JsonTools: Codeunit "Json Tools";
Cust: Record Customer;
begin
Cust.FindFirst();
Message('%1', JsonTools.RecToJson(Cust));
end;
The output is a complete JSON object with all normal and FlowField values, properly formatted field names, and no FlowFilter fields. Erik uses the DevToys Windows app (which includes a JSON formatter among many other developer utilities) to prettify and inspect the output.
What's Not Covered (Yet)
Erik acknowledges several areas that could be enhanced but are left for future work:
- Option fields — currently output as their numeric index; could be enhanced to output the option caption string instead
- Blob and Media fields — could be Base64-encoded into the JSON
- Zero dates — might warrant special handling (e.g., outputting
nullinstead of a zero date representation) - The reverse function (JSON to Record) — reading JSON back into a record requires building a dictionary that maps sanitized JSON key names back to actual field numbers
Erik also mentions that there's a function somewhere in the base app that "SQL-ifies" a field name (normalizes it for SQL compatibility), which would essentially do the same thing as the field name sanitization function — if anyone knows where it lives, he'd love to hear about it in the comments.
Summary
This video demonstrates how to build a reusable, generic RecToJson function in AL that works with any record type. The key techniques include using Variant and RecordRef for generic record handling, FieldRef for field-level iteration, proper type conversion through the Joker type, format number 9 for raw data formatting, and field name sanitization for clean JSON output. The code is available in Erik's YouTube GitHub repository for anyone to grab, use, or contribute to.