In this video, I look at how to create a function that creates a record variable from a JSON object. Check it out:

I also have a video on the opposite Record2Json.
In this video, Erik builds the companion function to the Rec2Json function created in a previous episode. This time, the goal is reversed: given a JSON object, create and populate a record variable in Business Central. Erik walks through the challenges of working with variants, record references, field type mapping, and building a field name lookup dictionary — all while live-coding and debugging in VS Code.
Setting the Stage: The Inverse of Rec2Json
In the sister episode, Erik created a Rec2Json function that takes a record (as a Variant) and returns a JsonObject. Now the challenge is the opposite: take a JsonObject and turn it into a populated record. The core question is straightforward, but the implementation requires careful handling of AL’s type system, especially around Variant and RecordRef.
Designing the Function Signature
The function needs to know which table to create a record for. Erik explores two approaches and decides to support both as overloads:
- Pass a table number (Integer) — the main implementation that does all the work.
- Pass a Variant (record) — a convenience overload that extracts the table number and delegates to the first.
An important nuance Erik highlights: when a parameter is declared as Variant (without var), you can pass a record in its place. But if you declare it as var Variant, the caller must pass an actual Variant variable. This is why the function returns a Variant rather than modifying the input parameter in place.
Here’s how the two overloads work together:
procedure Json2Rec(JO: JsonObject; Rec: Variant): Variant
var
Ref: RecordRef;
begin
Ref.GetTable(Rec);
exit(Json2Rec(JO, Ref.Number()));
end;
procedure Json2Rec(JO: JsonObject; TableNo: Integer): Variant
var
Ref: RecordRef;
FR: FieldRef;
FieldHash: Dictionary of [Text, Integer];
i: Integer;
JsonKey: Text;
T: JsonToken;
JsonKeyValue: JsonValue;
RecVar: Variant;
begin
Ref.OPEN(TableNo);
// ... implementation follows
end;
The first overload simply extracts the table number from the passed record and calls the second overload — clean and simple.
Building the Field Name Lookup Dictionary
The key challenge is mapping JSON keys back to field numbers. In the Rec2Json episode, Erik created a GetJsonFieldName function that converts AL field names into JSON-friendly keys (replacing special characters with underscores, collapsing double underscores, trimming leading/trailing underscores). Now, to go in reverse, Erik builds a Dictionary of [Text, Integer] that maps those JSON key names to their corresponding field numbers:
for i := 1 to Ref.FieldCount() do begin
FR := Ref.FieldIndex(i);
FieldHash.Add(GetJsonFieldName(FR), FR.Number);
end;
Note the use of FieldIndex rather than Field. The FieldIndex function gets the i-th field in sequence (first field, second field, etc.), regardless of what the actual field numbers are. The Field function would look up a specific field by number — so Field(21) gets field number 21, not the 21st field. This distinction matters when you’re iterating over all fields in a table.
The GetJsonFieldName function used for the mapping is the same one from the Rec2Json episode:
local procedure GetJsonFieldName(FRef: FieldRef): Text
var
Name: Text;
i: Integer;
begin
Name := FRef.Name();
for i := 1 to Strlen(Name) do begin
if Name[i] < '0' then
Name[i] := '_';
end;
exit(Name.Replace('__', '_').TrimEnd('_').TrimStart('_'));
end;
Iterating Over JSON Keys
With the lookup dictionary built, the next step is to iterate over the keys in the JSON object and assign values to the corresponding fields. Erik makes an important point about JSON: there is no guaranteed order of keys in a JSON object. The code must not depend on any particular field sequence.
The JsonObject.Keys() method returns a List of [Text], which supports foreach:
Ref.Init();
foreach JsonKey in JO.Keys() do begin
if JO.Get(JsonKey, T) then begin
if T.IsValue() then begin
JsonKeyValue := T.AsValue();
FR := Ref.Field(FieldHash.Get(JsonKey));
AssignValueToFieldRef(FR, JsonKeyValue);
end;
end;
end;
A few things to note here:
JO.Get()returns a Boolean indicating success and outputs aJsonToken— the chameleon data type that could be a value, an object, or an array.- The code checks
T.IsValue()before processing, since the function currently only handles simple values (not nested objects or arrays). - The
FieldHash.Get(JsonKey)call translates the JSON key back to a field number, which is then used withRef.Field()to get the appropriateFieldRef.
Assigning Values with Type Safety
You can't just assign any value to a FieldRef — you need to convert the JSON value to the correct AL type first. The FieldRef.Value property is a "joker" (it accepts various types), but the value you assign must match the field's actual type. Erik creates a dedicated procedure using a case statement on FR.Type():
local procedure AssignValueToFieldRef(var FR: FieldRef; JsonKeyValue: JsonValue)
begin
case FR.Type() of
FieldType::Code,
FieldType::Text:
FR.Value := JsonKeyValue.AsText();
FieldType::Integer:
FR.Value := JsonKeyValue.AsInteger();
FieldType::Date:
FR.Value := JsonKeyValue.AsDate();
else
error('%1 is not a supported field type', FR.Type());
end;
end;
Code and Text fields are handled together since the assignment is identical for both. The else branch raises an error for unsupported types, ensuring you don't silently lose data. In a production implementation, you'd want to add more types: Decimal, Boolean, DateTime, Time, BigInteger, Guid, and so on.
Erik notes that this procedure was scaffolded using a VS Code extension that provides AL code actions — when the compiler flags a call to a non-existent procedure, the extension offers to create the function stub with the correct parameter signatures automatically.
Returning the Result
One gotcha Erik catches during debugging: he initially forgot to return anything from the function. The RecordRef holds all the populated data, but you need to convert it back to a Variant for the return value:
RecVar := Ref;
exit(RecVar);
Testing the Function
The test is set up in a page extension on the Customer List, creating a simple JSON object and calling the function:
pageextension 74500 CustomerListExt extends "Customer List"
{
trigger OnOpenPage();
var
Json: Codeunit "Json Tools";
JsonObj: JsonObject;
C: Record Customer;
begin
JsonObj.ReadFrom('{ "No": "99999", "Name": "Yotube Demo","Invoice_Copies": "99999"}');
c := Json.Json2Rec(JsonObj, c);
message('%1', format(c));
end;
}
When you call Format on a record variable, you get all the fields as tab-separated text — a quick way to inspect the result. The debugging session confirms that the FieldHash dictionary is correctly built (with entries like "No" → 1, "Name" → 2, "Search" → 3, etc.), the JSON keys are resolved to the right field references, and the values are assigned correctly. The final message shows the Customer record with "99999" in the No. field, "Yotube Demo" in the Name field, and "99999" in Invoice Copies.
The Complete JsonTools Codeunit
Here is the full codeunit with both the Json2Rec and Rec2Json functions:
codeunit 74500 "Json Tools"
{
procedure Json2Rec(JO: JsonObject; Rec: Variant): Variant
var
Ref: RecordRef;
begin
Ref.GetTable(Rec);
exit(Json2Rec(JO, Ref.Number()));
end;
procedure Json2Rec(JO: JsonObject; TableNo: Integer): Variant
var
Ref: RecordRef;
FR: FieldRef;
FieldHash: Dictionary of [Text, Integer];
i: Integer;
JsonKey: Text;
T: JsonToken;
JsonKeyValue: JsonValue;
RecVar: Variant;
begin
Ref.OPEN(TableNo);
for i := 1 to Ref.FieldCount() do begin
FR := Ref.FieldIndex(i);
FieldHash.Add(GetJsonFieldName(FR), FR.Number);
end;
Ref.Init();
foreach JsonKey in JO.Keys() do begin
if JO.Get(JsonKey, T) then begin
if T.IsValue() then begin
JsonKeyValue := T.AsValue();
FR := Ref.Field(FieldHash.Get(JsonKey));
AssignValueToFieldRef(FR, JsonKeyValue);
end;
end;
end;
RecVar := Ref;
exit(RecVar);
end;
procedure Rec2Json(Rec: Variant): JsonObject
var
Ref: RecordRef;
Out: JsonObject;
FRef: FieldRef;
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
FRef.Class::Normal:
Out.Add(GetJsonFieldName(FRef), FieldRef2JsonValue(FRef));
FRef.Class::FlowField:
begin
FRef.CalcField();
Out.Add(GetJsonFieldName(FRef), FieldRef2JsonValue(FRef));
end;
end;
end;
exit(Out);
end;
local procedure FieldRef2JsonValue(FRef: FieldRef): JsonValue
var
V: JsonValue;
D: Date;
DT: DateTime;
T: Time;
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;
local procedure GetJsonFieldName(FRef: FieldRef): Text
var
Name: Text;
i: Integer;
begin
Name := FRef.Name();
for i := 1 to Strlen(Name) do begin
if Name[i] < '0' then
Name[i] := '_';
end;
exit(Name.Replace('__', '_').TrimEnd('_').TrimStart('_'));
end;
local procedure AssignValueToFieldRef(var FR: FieldRef; JsonKeyValue: JsonValue)
begin
case FR.Type() of
FieldType::Code,
FieldType::Text:
FR.Value := JsonKeyValue.AsText();
FieldType::Integer:
FR.Value := JsonKeyValue.AsInteger();
FieldType::Date:
FR.Value := JsonKeyValue.AsDate();
else
error('%1 is not a supported field type', FR.Type());
end;
end;
}
Limitations and Future Improvements
Erik is upfront that this is a foundational implementation. Getting data out of Business Central (Rec2Json) is significantly easier than getting data in (Json2Rec). Several areas need attention for a production-ready solution:
- Field validation: Currently, values are assigned directly without running field validation triggers. A more robust version would validate fields, potentially in field number order.
- Primary key handling: For inserting records, you'd want to process primary key fields first, insert the record, then validate the remaining fields.
- FlowFields: Assigning values to FlowFields serves no purpose since they're calculated — the function should skip them.
- More field types: The current implementation only handles Code, Text, Integer, and Date. Production code would need Decimal, Boolean, DateTime, Time, BigInteger, GUID, Option, Enum, and more.
- Blob and Media fields: These would likely need Base64 encoding/decoding for JSON transport.
- Nested objects and arrays: The current code only handles simple JSON values, not nested structures.
Summary
This episode demonstrates how to build a generic Json2Rec function in AL using RecordRef, FieldRef, and a dictionary-based field name lookup. The key technique is building a hash map from JSON-formatted field names to field numbers, then iterating over the JSON keys to populate the record. Combined with the Rec2Json function from the previous episode, this gives you a reusable library for bidirectional JSON-to-record conversion in Business Central — a solid foundation that can be extended with validation logic, additional type support, and more sophisticated data handling as needed.