The object oriented way of JSON handling in AL – Part 1

On the left side we have a normal JSON formatted object, on the right side we have a normal AL implementation of JSON generation

Item JSON Object

{
  itemNo: "123",
  price: 10.25,
  description: "Insane Item",
...
...
...
  active: true,
  variants: [
    {
      description: "Total insane variant"
    },
    {
      description: "not so insane variant"
    }
  ]
}

Normal AL Example

    procedure GenerateJSON(item: Record Item)
    var
        object: JsonObject;
        arrElement: JsonObject;
        arr: JsonArray;
        variants: Record "Item Variant";
    begin
        object.Add('itemNo', item."No.");
        object.Add('price', item."Unit Price");
        object.Add('description', item.Description);
        //...
        //...
        //...
        object.Add('active', not item.Blocked);


        variants.SetFilter("Item No.", item."No.");
        if variants.Find('-') then
            repeat
                Clear(arrElement);
                arrElement.Add('description', variants.Description);
                arr.Add(arrElement);
            until (variants.Next() = 0);

        if (arr.Count > 0) then
            object.Add('variants', arr);
    end;

Looks nice and easy to me, but what if your JSON gets more complicated and you need to reuse JSON blocks more often? Then you have to think about structure. I was looking for a flexible code structure which is easy adobtable.
The second point a was thinking about was, wouldn’t it be nice to just pass an object to a JSON Codeunit and the magic is happening behind?

Could we reduce the above code to the following?

item.ToJsonObject();

We are developers, of course we can.

The Components of the pattern

Assuming we are in a modern development every object (codeunit) knows how to convert to JSON. For that reason the first component is an interface “IJsonObjectConverter” which is implemented by every “object” codeunit.

interface IJsonObjectConverter
{
    procedure ToJsonObject(): JsonObject;
}
Since records can not implement interfaces we need a codeunit for the object. But to get the connection from the record to the codeunit I created a second interface
interface "IRecordToObjectConverter"
{
    procedure ConvertFromRecord(var recRef: RecordRef);
}

Now I can create the codeunit “ItemObject”

codeunit 50000 "ItemObject" implements IJsonObjectConverter, IRecordToObjectConverter
{
    ...Getter and setter...
    ...Some other stuff for another part of this blog
    ...

    procedure ToJsonObject() object: JsonObject;
    begin
        if GetItemNo() <> '' then
            object.Add('itemNo', itemNo);
        if GetPrice() <> 0.0 then
            object.Add('price', price);
        if GetDescription() <> '' then
            object.Add('description', description);
        if GetActive() <> true then
            object.Add('active', active);
    end;

    procedure ConvertFromRecord(var recRef: RecordRef);
    var
        Item: Record Item;
    begin
        if recRef.Number <> Database::Item then begin
            Error(errArgumentException);
        end;
        recRef.SetTable(Item);
        SetItemNo(Item."No.");
        SetDescription(Item.Description);
        SetPrice(Item."Unit Price");
        SetActive(not Item.Blocked);
    end;

    var
        itemNo: Code[20];
        price: Decimal;
        description: Text;
        active: Boolean;
}

Now we can export an item to json, but we want also add some variants. For this case we of course also need a codeunit for Item Variants

codeunit 50005 ItemVariantObject implements IJsonObjectConverter, IRecordToObjectConverter
{
    ...Getter and setter...
    ...Some other stuff for another part of this blog
    ...

   procedure ToJsonObject() object: JsonObject;
    begin
        if GetDescription() <> '' then
            object.Add('description', description);
    end;

    procedure ConvertFromRecord(var recRef: RecordRef);
    var
        ItemVariant: Record "Item Variant";
    begin
        if recRef.Number <> Database::"Item Variant" then begin
            Error(errArgumentException);
        end;
        recRef.SetTable(ItemVariant);
        SetDescription(ItemVariant.Description);
    end;

    var
        description: Text;
        active: Boolean;
}

I’m now also able to export Variants, but I need the link to the items to work. For this reason I add a new procedure “GetVariants” to the ItemObject. This procedure returns an JsonObjectList, I will descripe it in part 2 of this blogpost.
And of course adapt the procedure “ToJsonObject” from “ItemObject” to also export Variants

    procedure GetVariants() list: Codeunit JsonObjectlist;
    var
        ItemVariant: Record "Item Variant";
        ItemVariantObject: Codeunit ItemVariantObject;
        recRef: RecordRef;
    begin
        ItemVariant.SetRange("Item No.", GetItemNo());
        ItemVariant.SetLoadFields(Description);

        if ItemVariant.Find('-') then
            repeat
                recRef.GetTable(ItemVariant);
                ItemVariantObject := ItemVariantObject.Create();
                ItemVariantObject.ConvertFromRecord(recRef);
                list.AddNode(ItemVariantObject, ItemVariantObject);
            until ItemVariant.Next() = 0;
    end;

    procedure ToJsonObject() object: JsonObject;
    begin
        if GetItemNo() <> '' then
            object.Add('itemNo', itemNo);
        if GetPrice() <> 0.0 then
            object.Add('price', price);
        if GetDescription() <> '' then
            object.Add('description', description);
        if GetActive() <> true then
            object.Add('active', active);
        object.Add('variants', GetVariants().ToJsonArray());
    end;

Now the procedure from the first example looks like this.

    procedure GenerateJSON(item: Record Item)
    var
        itemObject: Codeunit ItemObject;
        recRef: RecordRef;
    begin
        recRef.GetTable(Item);
        itemObject.ConvertFromRecord(recRef);
        itemObject.ToJsonObject();
    end;

Conclusion

Everything mentionend above looks complicated at the first step. but I will descripe in follow ups why it is better approach.
You can see the whole example from here at GitHub. It also includes some implementations for the next blogpost
https://github.com/PatrickSchiefer/AL-JSON-Management-Demo

3 thoughts on “The object oriented way of JSON handling in AL – Part 1

  1. Pingback: The object oriented way of JSON handling in AL – Part 1 | Pardaan.com

  2. Sorry i may have understood something wrong, but i think your implementation is a little bit difficult.
    First of all i would think about structure. If i am right, we have two possible types of needed JSON’s structured or “flat”.

    I think “flat” records (f.e. Payment Methods, Language,…) are very easy to handle. You need a general codeunit with a Function called VariantToJSON(pRecVar:variant)result: JSONOBJECT. This function converts the variant to a recref and afterwards loops through the fields and adds the field and it’s value depending on fieldype to the resulting JSON-Object. Then you create a Table- Extension for every Table you want to have a RECORD.ToJSON() function. This function only contains a call to the VariantToJSON- function in the codeunit with the current record as parameter and returns the JSON from VariantToJSON.

    A little bit different is the structured JSON. It needs some more investigation, because you need to decide which depending data you want to return. (i think you do not want to return all Item Ledger Entries in a JSON). Here you create also a Table Extension with a function ToJSON() for the table (f.e. Item) this function may have Parameters do define the complexity of the returned JSON (with/without Variants, Texts, prices,…) or/and an overloaded version that only returns the flat record. This function contains the code to read and add the related records JSON by calling their ToJSON() or by calling VariantToJSON() directly with the record.

    This all is extendable because you may include events where other add-ons may add their data to your JSON, and does not need any interfaces.

    • I agree with you this solution is difficult, because you need to understand some object oriented approaches to work with it.
      Your solution also fine but less flexible, this is the reason why the headline of this blog is “the object oriented way” I’m sure there are many other ways to do.

      So why I choose this way?
      Imagine you want to write a library for http request. You could implement procedure “PostJson(url; json)”
      In a normal case you would pass two strings to this procedure, in my case I could pass an parameter of the interface.
      So what is the advantage?
      When you look at my example there is some code within the json generation, there are even sql queries. I want my sql queries only to be executed when neccassary because the are expensive.
      In my case the are executed when they are needed and I don’t have to care about it.

Leave a Reply to patrickschieferCancel reply