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! 😊
[…] Esta funcionalidad utiliza la codeunit “Mgt. Exp/Imp Table To Json”, que ya te mostré en un post anterior. […]