Cómo Importar y Exportar Tablas en Business Central Utilizando JSON

Aprende a importar y exportar tablas completas en Business Central usando JSON con este detallado código en AL.

La programación en Microsoft Dynamics 365 Business Central ofrece herramientas poderosas para manejar datos de manera eficiente. Un ejemplo perfecto es el manejo de tablas utilizando JSON. Con este enfoque, se facilita la importación y exportación de datos, simplificando procesos que de otra manera serían complicados. Este post te guiará a través de un código en AL que permite realizar estas operaciones de manera efectiva y sencilla.

Este desarrollo se realizó para poder exportar e importar toda una tabla de Business Central a un JSON y así ser más ágil y facilitar las exportaciones e importaciones de datos. Exportar e importar tablas completas puede ser una tarea tediosa y propensa a errores si se hace manualmente. Sin embargo, utilizando JSON, podemos automatizar y agilizar este proceso, asegurando que los datos se transfieran de manera eficiente y precisa entre diferentes entornos y sistemas.

El código que presento a continuación permite convertir registros de tablas de Business Central en objetos JSON y viceversa, facilitando la integración de datos con otros sistemas que también utilicen JSON como formato de intercambio. Además, este enfoque mejora la flexibilidad y la adaptabilidad del sistema, permitiendo manejar grandes volúmenes de datos de manera rápida y eficaz.

¡Vamos manos a la obra! 😶‍🌫️

Bloque de Importacion

Leyendo Arrays JSON

procedure ReadJsonArray(JArray: JsonArray; var RecordRefe: RecordRef)
var
    JToken: JsonToken;
    JObject: JsonObject;
begin
    Clear(JToken);
    foreach JToken in JArray do begin
        Clear(JObject);
        JObject := JToken.AsObject();
        JsonToRec(JObject, RecordRefe);
    end;
end;

Esta función recorre un array JSON (JArray) y convierte cada objeto JSON (JObject) en un registro de tabla referenciado (RecordRefe). Utiliza un bucle foreach para iterar sobre cada token JSON en el array, convirtiéndolo en un objeto JSON y luego llamando a JsonToRec para procesarlo. Esta función se utiliza para manejar grandes volúmenes de datos que se encuentran en formato JSON, permitiendo su conversión a registros de tablas de Business Central de manera automatizada.

Convertir JSON a Registro

procedure JsonToRec(JObject: JsonObject; var RecordRefe: RecordRef)
var
    FieldRefe: FieldRef;
    FieldHash: Dictionary of [Text, Integer];
    i: Integer;
    JsonKey: Text;
    JToken: JsonToken;
    JsonKeyValue: JsonValue;
    NumField: Integer;
begin
    for i := 1 to RecordRefe.FieldCount() do begin
        FieldRefe := RecordRefe.FieldIndex(i);
        FieldHash.Add(GetJsonFieldName(FieldRefe), FieldRefe.Number);
    end;
    RecordRefe.Init();
    if ChangeCompany <> '' then
        RecordRefe.ChangeCompany(ChangeCompany);

    foreach JsonKey in JObject.Keys() do
        if JObject.Get(JsonKey, JToken) then
            if JToken.IsValue() then begin
                JsonKeyValue := JToken.AsValue();
                if FieldHash.ContainsKey(JsonKey) then begin
                    NumField := FieldHash.Get(JsonKey);
                    FieldRefe := RecordRefe.Field(NumField);
                    AssignValueToFieldRef(FieldRefe, RecordRefe, JsonKeyValue);
                end;
            end;

    if OverWrite then begin
        if not RecordRefe.Insert(true) then
            RecordRefe.Modify(true);
    end else
        RecordRefe.Insert(true);
end;

JsonToRec toma un objeto JSON (JObject) y lo convierte en un registro de tabla (RecordRefe). Primero, crea un diccionario (FieldHash) que mapea nombres de campos JSON a números de campos en el registro, lo que facilita la identificación y asignación de valores. Luego, inicializa el registro y cambia de compañía si es necesario. Recorre las claves del objeto JSON, asignando valores a los campos del registro según sea apropiado. Finalmente, inserta o modifica el registro basado en la variable OverWrite. Esta función se utiliza para la importación precisa de datos, asegurando que cada campo del JSON se asigne correctamente al campo correspondiente en el registro de la tabla.

Asignar Valores a Campos

local procedure AssignValueToFieldRef(var FieldRefe: FieldRef; var RecordRefe: RecordRef; JsonKeyValue: JsonValue)
var
    ValueGuid: Guid;
begin
    if ImportTablesWithSpecialFields(FieldRefe, RecordRefe, JsonKeyValue) then
        exit;

    case FieldRefe.Type() of
        FieldType::Code,
        FieldType::Text:
            FieldRefe.Value := JsonKeyValue.AsText();
        FieldType::Integer:
            FieldRefe.Value := JsonKeyValue.AsInteger();
        FieldType::Date:
            FieldRefe.Value := JsonKeyValue.AsDate();
        FieldType::Time:
            FieldRefe.Value := JsonKeyValue.AsTime();
        FieldType::DateTime:
            FieldRefe.Value := JsonKeyValue.AsDateTime();
        FieldType::Decimal:
            FieldRefe.Value := JsonKeyValue.AsDecimal();
        FieldType::Option:
            FieldRefe.Value := JsonKeyValue.AsOption();
        FieldType::Boolean:
            FieldRefe.Value := JsonKeyValue.AsBoolean();
        FieldType::Guid:
            begin
                Evaluate(ValueGuid, JsonKeyValue.AsText());
                FieldRefe.Value := ValueGuid;
            end;
        FieldType::MediaSet,
        FieldType::Media,
        FieldType::Blob:
            ConvertBase64ToFieldsWithStream(FieldRefe, JsonKeyValue);
    end;
end;

AssignValueToFieldRef asigna valores de JSON a los campos del registro (FieldRefe). Dependiendo del tipo de campo, extrae y asigna el valor apropiado del JSON. Para campos de tipo Code, Text, Integer, Date, Time, DateTime, Decimal, Option, Boolean y Guid, se asigna directamente el valor correspondiente. Para campos especiales como MediaSet, Media o Blob, llama a la función ConvertBase64ToFieldsWithStream para manejar la conversión adecuada. Esta función se utiliza para asegurar que los valores se asignen correctamente según el tipo de campo, evitando errores y garantizando la integridad de los datos.

Campos Especiales de Tablas

local procedure ImportTablesWithSpecialFields(var FieldRefe: FieldRef; var RecordRefe: RecordRef; JsonKeyValue: JsonValue) ReturnValue: Boolean
var
    Item: Record Item;
    EntityText: Record "Entity Text";
    l_FieldRef: FieldRef;
    ValueInteger: Integer;
    ValueText: Text;
    ValueGuid: Guid;
begin
    ReturnValue := false;
    case RecordRefe.Number of
        Database::"Entity Text":
            case FieldRefe.Number of
                EntityText.FieldNo(Company):
                    begin
                        ValueText := JsonKeyValue.AsText();
                        if Item.Get(ValueText) then begin
                            Clear(l_FieldRef);
                            l_FieldRef := RecordRefe.Field(EntityText.FieldNo("Source System Id"));
                            l_FieldRef.Value := Item.SystemId;
                            ValueItemGuid := Item.SystemId;
                        end;
                        FieldRefe.Value := CompanyName();
                        ReturnValue := true;
                    end;

                EntityText.FieldNo("Source System Id"):
                    begin
                        Clear(l_FieldRef);
                        l_FieldRef := RecordRefe.Field(EntityText.FieldNo("Source System Id"));

                        if Evaluate(ValueGuid, format(l_FieldRef.Value)) then begin
                            Clear(l_FieldRef);
                            l_FieldRef := RecordRefe.Field(EntityText.FieldNo("Source Table Id"));
                            if Evaluate(ValueInteger, Format(l_FieldRef.Value)) then
                                if ValueInteger = Database::Item entonces
                                    Item.Reset();
                                    Item.SetRange(SystemId, ValueGuid);
                                    if Item.FindFirst() entonces
                                        FieldRefe.Value := Item.SystemId;
                                        ReturnValue := true;
                                    end;
                                end;
                        end;
                    end;
            end;
    end;
end;

Esta función gestiona la importación de campos especiales para tablas específicas. Por ejemplo, para la tabla Entity Text, maneja campos como Company y Source System Id de manera particular, buscando y asignando valores basados en condiciones específicas. La función utiliza registros temporales y referencias de campos para realizar estas asignaciones de manera precisa. Esta función se utiliza cuando se trabaja con tablas que tienen campos que requieren lógica especial para su importación, asegurando que los datos se importen correctamente sin perder información crítica.

Convertir Base64 a Campos con Stream

local procedure ConvertBase64ToFieldsWithStream(var FieldRefe: FieldRef; JsonKeyValue: JsonValue)
var
    TempConfigMediaBuffer: Record "Config. Media Buffer" temporary;
    Base64Convert: Codeunit "Base64 Convert";
    TempBlob: Codeunit "Temp Blob";
    OutStream: OutStream;
    InStream: InStream;
    Base64Text: Text;
begin
    Clear(TempBlob);
    Clear(Base64Convert);
    Clear(OutStream);
    Clear(InStream);
    Clear(TempConfigMediaBuffer);
    Clear(Base64Text);

    // Traspasamos

 el valor en base64 a una variable
    Base64Text := JsonKeyValue.AsText();

    // Convertimos el Base64 a InStream
    si FieldRefe.Type() en [FieldType::Media, FieldType::MediaSet] entonces
        TempBlob.CreateOutStream(OutStream);
        Base64Convert.FromBase64(Base64Text, OutStream);
        TempBlob.CreateInStream(InStream);
    end;

    // Añadimos el Stream a una temporal con el campo necesario. Para después añadirlo como valor al campo de la tabla referenciada
    case FieldRefe.Type() of
        FieldType::MediaSet:
            begin
                TempConfigMediaBuffer."Media Set".ImportStream(InStream, '');
                FieldRefe.Value := TempConfigMediaBuffer."Media Set";
            end;
        FieldType::Media:
            begin
                TempConfigMediaBuffer."Media".ImportStream(InStream, '');
                FieldRefe.Value := TempConfigMediaBuffer."Media";
            end;
        FieldType::Blob:
            begin
                TempConfigMediaBuffer."Media Blob".CreateOutStream(OutStream);
                Base64Convert.FromBase64(Base64Text, OutStream);
                FieldRefe.Value := TempConfigMediaBuffer."Media Blob";
            end;
    end;

    // Hacemos el clear del campo de la tabla temporal para vaciarlo y que no se queden restos del archivo
    Clear(TempConfigMediaBuffer."Media");
    Clear(TempConfigMediaBuffer."Media Set");
    Clear(TempConfigMediaBuffer."Media Blob");
    Clear(TempConfigMediaBuffer);
end;

ConvertBase64ToFieldsWithStream convierte datos codificados en base64 a tipos de campo Media, MediaSet o Blob. Utiliza un flujo de entrada y salida (InStream, OutStream) para realizar la conversión, asignando el valor resultante al campo de la tabla. Este proceso implica la creación de flujos temporales y el uso de un buffer de configuración temporal para manejar los datos de manera eficiente. Esta función se utiliza para manejar datos multimedia y binarios que se encuentran en formato base64, permitiendo su almacenamiento adecuado en los campos correspondientes de la tabla.

Bloque de Exportaciones

Añadiendo Arrays JSON

procedure AddJsonArray(var JObject: JsonObject; RecVariant: Variant)
var
    RecordRefe: RecordRef;
    JArray: JsonArray;
begin
    Clear(JArray);
    Clear(RecordRefe);
    RecordRefe.GetTable(RecVariant);
    si RecordRefe.FindSet() entonces
        repeat
            JArray.Add(RecToJson(RecordRefe));
        until RecordRefe.Next() = 0;
        JObject.Add(Format(RecordRefe.Number), JArray);
    end;
end;

AddJsonArray toma un objeto JSON (JObject) y añade un array JSON (JArray) basado en un conjunto de registros (RecVariant). Utiliza RecToJson para convertir cada registro a un objeto JSON y lo añade al array. Esta función se utiliza para exportar grandes conjuntos de datos en formato JSON, facilitando su transferencia y almacenamiento en otros sistemas.

Convertir Registro a JSON

procedure RecToJson(RecordRefe: RecordRef): JsonObject
var
    FieldRefe: FieldRef;
    JObject: JsonObject;
    JValue: JsonValue;
    i: Integer;
begin
    for i := 1 to RecordRefe.FieldCount() do begin
        Clear(JValue);
        FieldRefe := RecordRefe.FieldIndex(i);
        case FieldRefe.Class of
            FieldRefe.Class::Normal,
            FieldRefe.Class::FlowField:
                begin
                    if FieldRefe.Class = FieldRefe.Class::FlowField entonces
                        FieldRefe.CalcField();
                    JValue := FieldRefToJsonValue(FieldRefe, RecordRefe);
                    si no JValue.IsNull entonces
                        JObject.Add(GetJsonFieldName(FieldRefe), JValue);
                end;
        end;
    end;
    exit(JObject);
end;

RecToJson convierte un registro de tabla (RecordRefe) en un objeto JSON (JObject). Recorre cada campo del registro, convierte su valor a JSON usando FieldRefToJsonValue y lo añade al objeto JSON. Esta función se utiliza para la exportación de datos, permitiendo que los registros de tablas se transformen en un formato JSON fácilmente utilizable por otros sistemas.

Convertir Campo a Valor JSON

local procedure FieldRefToJsonValue(FieldRefe: FieldRef; RecordRefe: RecordRef): JsonValue
var
    JValue: JsonValue;
    ValueText: Text;
    ValueDate: Date;
    ValueDateTime: DateTime;
    ValueTime: Time;
    ValueInt: Integer;
    ValueDec: Decimal;
    ValueOption: Option;
    ValueBoo: Boolean;
begin
    si ExportTablesWithSpecialFields(FieldRefe, RecordRefe) entonces
        exit;

    Clear(JValue);

    case FieldRefe.Type() of
        FieldType::Code,
        FieldType::Text:
            begin
                ValueText := Format(FieldRefe.Value, 0, 9);
                JValue.SetValue(ValueText);
            end;
        FieldType::Integer:
            begin
                ValueInt := FieldRefe.Value;
                JValue.SetValue(ValueInt);
            end;
        FieldType::Date:
            begin
                ValueDate := FieldRefe.Value;
                JValue.SetValue(ValueDate);
            end;
        FieldType::Time:
            begin
                ValueTime := FieldRefe.Value;
                JValue.SetValue(ValueTime);
            end;
        FieldType::DateTime:
            begin
                ValueDateTime := FieldRefe.Value;
                JValue.SetValue(ValueDateTime);
            end;
        FieldType::Decimal:
            begin
                ValueDec := FieldRefe.Value;
                JValue.SetValue(ValueDec);
            end;
        FieldType::Option:
            begin
                ValueOption := FieldRefe.Value;
                JValue.SetValue(ValueOption);
            end;
        FieldType::Boolean:
            begin
                ValueBoo := FieldRefe.Value;
                JValue.SetValue(ValueBoo);
            end;
        FieldType::Guid:
            begin
                ValueText := Format(FieldRefe.Value);
                JValue.SetValue(ValueText);
            end;
        FieldType::Blob,
        FieldType::MediaSet,
        FieldType::Media:
            begin
                ValueText := ConvertFieldsWithStreamToBase64(FieldRefe);
                si ValueText <> '' entonces
                    JValue.SetValue(ValueText);
            end;
    end;
    exit(JValue);
end;

FieldRefToJsonValue convierte un campo de registro (FieldRefe) a un valor JSON (JValue). Dependiendo del tipo de campo, obtiene y asigna el valor apropiado. Para campos de tipo Code, Text, Integer, Date, Time, DateTime, Decimal, Option, Boolean y Guid, se formatea y asigna el valor correspondiente. Para campos especiales como Blob, MediaSet y Media, llama a ConvertFieldsWithStreamToBase64 para realizar la conversión adecuada. Esta función se utiliza para asegurar que los valores de los campos se transformen correctamente a formato JSON, preservando la integridad de los datos durante la exportación.

Campos Especiales en Exportación

local procedure ExportTablesWithSpecialFields(var FieldRefe: FieldRef; RecordRefe: RecordRef) ReturnValue: Boolean
var
    Item: Record Item;
    EntityText: Record "Entity Text";
    l_FieldRef: FieldRef;
    ValueInteger: Integer;
begin
    ReturnValue := false;
    case RecordRefe.Number of
        Database::"Entity Text":
            case FieldRefe.Number of
                EntityText.FieldNo(Company):
                    begin
                        Clear(l_FieldRef);
                        l_FieldRef := RecordRefe.Field(EntityText.FieldNo("Source Table Id"));
                        Evaluate(ValueInteger, Format(l_FieldRef.Value));
                        si ValueInteger = Database::Item entonces
                            Clear(l_FieldRef);
                            l_FieldRef := RecordRefe.Field(EntityText.FieldNo("Source System Id"));
                            Item.Reset();
                            Item.SetRange(SystemId, EntityText."Source System Id");
                            si Item.FindFirst() entonces
                                FieldRefe.Value := Item."No.";
                                ReturnValue := true;
                            end;
                        end;
                    end;
            end;
    end;
end;

Esta función maneja la exportación de campos especiales para tablas específicas, como la tabla Entity Text. Realiza búsquedas y asignaciones específicas para estos campos, asegurando que se exporten correctamente. Utiliza registros temporales y referencias de campos para realizar estas operaciones de manera precisa. Esta función se utiliza cuando se exportan datos de tablas con campos que requieren lógica especial, asegurando que los datos exportados sean precisos y completos.

Convertir Campos con Stream a Base64

local procedure ConvertFieldsWithStreamToBase64(FieldRefe: FieldRef) ReturnValue: Text
var
    TempConfigMediaBuffer: Record "Config. Media Buffer" temporary;
    TenantMedia: Record "Tenant Media";
    Base64Convert: Codeunit "Base64 Convert";
    InStream: InStream;
    ValueIdMediaSet: Text;
    IsExit: Boolean;
    Count: Integer;
begin
    Clear(InStream);
    Clear(Base64Convert);
    Clear(ValueIdMediaSet);
    Clear(TempConfigMediaBuffer);
    ReturnValue := '';
    IsExit

 := false;

    case FieldRefe.Type() of
        FieldType::MediaSet:
            begin
                TempConfigMediaBuffer."Media Set" := FieldRefe.Value;
                Count := TempConfigMediaBuffer."Media Set".Count;

                si Count = 0 entonces
                    IsExit := true
                else
                    // Añadimos el valor del ID a una variable para buscarlo
                    ValueIdMediaSet := Format(TempConfigMediaBuffer."Media Set".Item(1));
            end;
        FieldType::Media:
            begin
                TempConfigMediaBuffer.Media := FieldRefe.Value;

                si no TempConfigMediaBuffer."Media".HasValue entonces
                    IsExit := true
                else
                    // Añadimos el valor del ID a una variable para buscarlo
                    ValueIdMediaSet := Format(TempConfigMediaBuffer."Media".MediaId);
            end;
        FieldType::Blob:
            begin
                // Calculamos el Blob si no está sale
                si no FieldRefe.CalcField() entonces
                    IsExit := true
                else
                    // Añadimos el Blob en la temporal
                    TempConfigMediaBuffer."Media Blob" := FieldRefe.Value;

                si TempConfigMediaBuffer."Media Blob".Length < 1 entonces
                    IsExit := true;
            end;
    end;

    // Hacemos el clear de los campos de la tabla temporal para vaciarlo y que no se queden restos del archivo
    si IsExit entonces
        Clear(TempConfigMediaBuffer."Media");
        Clear(TempConfigMediaBuffer."Media Set");
        Clear(TempConfigMediaBuffer."Media Blob");
        Clear(TempConfigMediaBuffer);
        exit;
    end;

    // Buscamos el valor en el Tenant Media y creamos el InStream para convertirlo en un Base64
    si FieldRefe.Type() en [FieldType::Media, FieldType::MediaSet] entonces
        TenantMedia.Reset();
        TenantMedia.Get(ValueIdMediaSet);
        TenantMedia.CalcFields(Content);
        TenantMedia.Content.CreateInStream(InStream);
        ReturnValue := Base64Convert.ToBase64(InStream);
    end;

    si FieldRefe.Type() = FieldType::Blob entonces
        // Buscamos el valor en el Tenant Media y creamos el InStream para convertirlo en un Base64
        TempConfigMediaBuffer."Media Blob".CreateInStream(InStream);
        ReturnValue := Base64Convert.ToBase64(InStream);
    end;

    // Hacemos el clear del campo de la tabla temporal para vaciarlo y que no se queden restos del archivo
    Clear(TempConfigMediaBuffer."Media");
    Clear(TempConfigMediaBuffer."Media Set");
    Clear(TempConfigMediaBuffer."Media Blob");
    Clear(TempConfigMediaBuffer);
end;

ConvertFieldsWithStreamToBase64 convierte campos de tipo Media, MediaSet o Blob a base64. Utiliza flujos de entrada y salida para la conversión y realiza operaciones específicas dependiendo del tipo de campo. Este proceso implica la búsqueda de valores en el Tenant Media y la creación de flujos para la conversión a base64. Esta función se utiliza para exportar datos multimedia y binarios en un formato que sea fácilmente transferible y almacenable en otros sistemas.

Bloque de Funciones Comunes

Sobreescribiendo Datos

procedure SetOverWrite(NewOverWrite: Boolean)
begin
    OverWrite := NewOverWrite;
end;

SetOverWrite establece el valor de la variable OverWrite que indica si se sobrescribirán los datos existentes durante la importación. Esta función se utiliza para controlar el comportamiento de la importación, permitiendo elegir si se deben actualizar los registros existentes o solo insertar nuevos registros.

Cambiando de Compañía

procedure SetChangeCompany(NewChangeCompany: Text)
begin
    ChangeCompany := NewChangeCompany;
end;

SetChangeCompany establece el nombre de la compañía para cambiar el contexto de la compañía durante la importación/exportación. Esta función es importante para trabajar con múltiples compañías en Business Central, permitiendo cambiar el contexto de los datos según sea necesario.

Obteniendo Nombres de Campos JSON

local procedure GetJsonFieldName(FieldRefe: FieldRef): Text
var
    Name: Text;
    i: Integer;
begin
    Name := FieldRefe.Name();
    for i := 1 to Strlen(Name) do
        si Name[i] < '0' entonces
            Name[i] := '_';
    exit(Name.Replace('__', '_').TrimEnd('_').TrimStart('_'));
end;

GetJsonFieldName obtiene el nombre del campo JSON correspondiente al FieldRef, reemplazando caracteres no válidos y ajustando el nombre para asegurar su validez en JSON. Esta función se utiliza para la conversión de nombres de campos, asegurando que los nombres sean compatibles con el formato JSON y evitando errores durante la importación/exportación.

Beneficios y Aplicaciones Prácticas

Este código proporciona una solución eficiente para manejar la importación y exportación de tablas en Business Central utilizando JSON. Al permitir la conversión directa de datos entre registros de tablas y estructuras JSON, se simplifican muchas tareas de integración y migración de datos. Los beneficios incluyen:

  • Automatización: Simplifica la automatización de procesos de importación y exportación.
  • Eficiencia: Reduce el tiempo y esfuerzo necesarios para manejar grandes volúmenes de datos.
  • Flexibilidad: Facilita la integración con otros sistemas que utilizan JSON como formato de intercambio de datos.
  • Mantenimiento: Mejora el mantenimiento y actualización de datos en diferentes entornos y compañías.

Conclusión

El uso de JSON para importar y exportar tablas en Business Central es una técnica poderosa que puede mejorar significativamente la eficiencia y flexibilidad en la gestión de datos. Este código en AL demuestra cómo se pueden implementar estas operaciones de manera efectiva, proporcionando una herramienta valiosa para los desarrolladores de Business Central.

Si quieres ver el código completo, está en GitHub.

Espero que esta guía te haya sido de ayuda. ¡Hasta la próxima! 😊

Share your love

One comment

Leave a Reply

Your email address will not be published. Required fields are marked *