Genesys Cloud - Developer Community!

 View Only

Sign Up

  • 1.  Handling nested arrays in Data Action responses

    Posted 8 days ago

    Olá, comunidade!

    Tenho um problema recorrente ao integrar APIs externas com Data Actions: respostas que possuem arrays dentro de outros arrays.

    Dependendo da estrutura do payload, o tratamento no Translation Map, no Success Template ou diretamente no Architect pode ficar complexo e difícil de manter.

    As soluções normalmente sugeridas são:

    - Solicitar que o cliente refatore a API.
    - Criar uma camada intermediaria para normalizar o payload.
    - Utilizar uma função ou serviço externo para transformar a resposta antes de envia-la ao Genesys Cloud.

    Em alguns cenarios, tenho utilizado outra abordagem: transformo parte da resposta em string e depois reconstruo o JSON dentro do Architect. Essa estrategia funciona, mas pode aumentar a complexidade do fluxo e exige bastante cuidado com validacao e manutencao.

    Alguem utiliza uma abordagem diferente para tratar arrays aninhados?

    Gostaria de conhecer experiencias com Translation Maps, Success Templates, JSONPath, Velocity, Genesys Cloud Functions ou outros padrões para achatar, percorrer ou reorganizar esse tipo de estrutura.

    Qual abordagem apresentou melhor resultado em manutenção, desempenho e facilidade de suporte?


    ________________________________________________________________________________________________________________________________________________________________________

    Hello, community!

    I frequently face a challenge when integrating external APIs with Data Actions: responses that contain arrays nested inside other arrays.

    Depending on the payload structure, handling the response in the Translation Map, Success Template, or directly in Architect can become complex and difficult to maintain.

    The solutions usually suggested are:

    - Ask the customer to refactor the API.
    - Create an intermediate layer to normalize the payload.
    - Use an external function or service to transform the response before sending it to Genesys Cloud.

    In some scenarios, I use another approach: I convert part of the response into a string and then rebuild the JSON inside Architect. This strategy works, but it can increase flow complexity and requires careful validation and maintenance.

    Does anyone use a different approach to handle nested arrays?

    I would like to hear about experiences with Translation Maps, Success Templates, JSONPath, Velocity, Genesys Cloud Functions, or other patterns for flattening, iterating over, or restructuring this type of payload.

    Which approach has provided the best results in terms of maintainability, performance, and supportability?


    #Architect
    #DataActions

    ------------------------------
    Matheus Mendonca
    ------------------------------


  • 2.  RE: Handling nested arrays in Data Action responses

    Posted 8 days ago

    Olá, @Matheus Mendonca! Tudo bom?

    Esse é um cenário bem comum nas Data Actions. O principal ponto é que o output contract não suporta arrays aninhados. Então, mesmo que o JSONPath consiga encontrar a informação na resposta original, no final ainda é necessário reorganizar os dados em uma estrutura compatível com o contrato da ação e com o Architect.

    Eu normalmente separo a solução em três possibilidades:

    Para estruturas simples, utilizaria o Translation Map para extrair somente os campos necessários e o Success Template para montar uma resposta mais simples. Em vez de devolver todo o payload original, retornaria apenas os valores que o fluxo realmente precisa, como propriedades individuais, collections ou um array simples de objetos.

    Para transformações mais específicas, o Velocity pode ajudar na montagem do Success Template. Existem também macros como `firstFromArray` e `moveKeysIntoArrayOfObjects`, mas eu utilizaria essa abordagem somente quando a estrutura da resposta for estável. Conforme aumentam as condições e os níveis do payload, o template pode ficar bem difícil de entender, testar e manter.

    Para respostas dinâmicas, com vários níveis de arrays ou regras mais complexas, acredito que o melhor caminho seja utilizar uma Data Action Function. Ela pode atuar como uma camada de adaptação: recebe o payload original, percorre e reorganiza os arrays e devolve uma estrutura simples e específica para o Architect.

    A abordagem de retornar o conteúdo como string e utilizar `JsonParse` no Architect também é válida, principalmente com a possibilidade de trabalhar com variáveis JSON e converter os valores em collections usando `ToJsonCollection`.

    Porém, eu usaria essa solução somente em casos pontuais. Ela acaba transferindo para o fluxo a responsabilidade de validar o JSON, tratar propriedades ausentes, controlar índices e percorrer collections. Dependendo do tamanho do fluxo, isso pode dificultar bastante a manutenção e o suporte.

    Também tentaria evitar devolver informações que o fluxo não utiliza. Quanto menor e mais específico for o output contract, mais simples ficam os testes, a manutenção e o tratamento de erros.

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    Hello!

    This is a very common scenario when working with Data Actions. The main point is that the output contract does not support nested arrays. So, even if JSONPath can find the information in the original response, the data still needs to be reorganized into a structure that is compatible with both the action contract and Architect.

    I usually separate the solution into three possible approaches:

    For small and predictable structures, I would use the Translation Map to extract only the required fields and the Success Template to build a simpler response. Instead of returning the entire original payload, I would return only the values the flow actually needs, such as individual properties, collections, or a simple array of objects.

    For more specific transformations, Velocity can help when building the Success Template. There are also macros such as `firstFromArray` and `moveKeysIntoArrayOfObjects`, but I would use this approach only when the response structure is stable. As the number of conditions and payload levels increases, the template can become difficult to understand, test, and maintain.

    For dynamic responses with multiple array levels or more complex transformation rules, I believe the best approach is to use a Data Action Function. It can act as an adaptation layer: it receives the original payload, processes and reorganizes the arrays, and returns a simpler structure designed specifically for Architect.

    Returning the content as a string and using `JsonParse` in Architect is also a valid approach, especially now that we can work with JSON variables and convert values into collections using `ToJsonCollection`.

    However, I would use this solution only for specific cases. It moves the responsibility for validating the JSON, handling missing properties, controlling indexes, and iterating through collections into the flow itself. Depending on the flow size, this can make maintenance and support much more difficult.

    I would also avoid returning information that the flow does not use. The smaller and more specific the output contract is, the easier testing, maintenance, and error handling become.



    ------------------------------
    Arthur Pereira Reinoldes
    ------------------------------



  • 3.  RE: Handling nested arrays in Data Action responses

    Posted 3 days ago

    Great summary Arthur! We've also found that once the transformation logic starts becoming more complex, centralising it in a Genesys Cloud Function makes it much easier to maintain. Keeping the parsing and restructuring in one place means Architect stays much cleaner, and any future changes only need to be made once rather than across multiple Data Actions or flows.

    We also try to return only the data that Architect actually needs, which keeps the payload smaller and simplifies troubleshooting.



    ------------------------------
    Phaneendra
    Technical Solutions Consultant
    ------------------------------



  • 4.  RE: Handling nested arrays in Data Action responses

    Posted 8 days ago

    I agree with Arthur's points. In my experience, the biggest challenge isn't just handling nested arrays-it's deciding where that complexity should live.

    Whenever possible, I try to keep Architect focused on orchestration rather than data transformation. If I find myself adding multiple loops, index handling, or extensive JSON parsing in the flow, that's usually a sign that the payload should be simplified before it reaches Architect.

    That said, there have been cases where I returned part of the response as a string and reconstructed the JSON in Architect. It works and can be a practical workaround, especially when changing the API or adding a middleware layer isn't an option. However, I see it as an exception rather than a design pattern because it tends to make flows harder to maintain over time.

    One enhancement that I think would add a lot of value would be native support for nested arrays in Data Actions. It would reduce the need for workarounds and make integrations with more complex APIs much easier.

    Great discussion-I'm interested to see if anyone has found other patterns or best practices for handling this scenario.



    ------------------------------
    Raphael Poliesi
    ------------------------------



  • 5.  RE: Handling nested arrays in Data Action responses

    Posted 7 days ago

    First, as a developer on the Data Action service since day 1, I am thrilled to see forum threads with such a great grasp on the ecosystem and the best way to handle different scenarios. I agree 100% with the both of the replies to the original question:

    Use the translation map to cut down the data you have to deal with as much as possible.

    You can work with JSON in Architect, but it isn't a great place for really complex JSON work.

    If the data is dynamic or really complex then Data Action Functions allow you to work with the response using node, which is pretty much the best possible tool for complex JSON manipulation.

    > One enhancement that I think would add a lot of value would be native support for nested arrays in Data Actions. It would reduce the need for workarounds and make integrations with more complex APIs much easier.

    Data actions actually can handle nested arrays. The limitation is when the service attempts to "flatten" the results for services like Architect. If you try out a data action with nested arrays in test mode, and uncheck the "flatten" box, it should work fine. Since Architect is one of the biggest consumers of data actions it generally forces all of the data actions to meet the requirements of "flattening", such as no nested arrays.



    ------------------------------
    --Jason
    ------------------------------



  • 6.  RE: Handling nested arrays in Data Action responses

    Posted 4 days ago

    Excelente ponto, @Matheus Mendonca. Já passei por esse cenário algumas vezes e, na minha experiência, depende muito da frequência com que a API muda e do nível de complexidade do payload.

    Sempre que possível, prefiro evitar "remontar" JSON dentro do Architect. Funciona, mas acaba deixando o fluxo mais difícil de entender e manter, principalmente quando outra pessoa precisa dar suporte posteriormente.

    Quando a estrutura é simples, costumo resolver com Translation Map + Success Template, extraindo apenas os campos realmente necessários para o fluxo. Já para respostas com vários níveis de arrays ou estruturas muito dinâmicas, normalmente considero mais vantajoso normalizar a resposta antes de chegar ao Genesys Cloud (normalmento, peço ajuste na API), pois isso reduz bastante a complexidade do Architect.

    No fim, quanto mais simples o payload entregue ao Architect, mais fácil fica a manutenção e o suporte.



    ------------------------------
    Kayanne Novaes
    na
    ------------------------------



  • 7.  RE: Handling nested arrays in Data Action responses

    Posted 3 days ago

    Tenho analisado diferentes abordagens para tratar respostas de APIs que contêm arrays de objetos JSON em Data Actions do Genesys Cloud.

    Um ponto que gostaria de deixar claro é que não considero que encaminhar essa estrutura para o Architect adicione complexidade desnecessária ao fluxo.

    Na minha visão, ocorre justamente o contrário.

    Quando uma API retorna um array de objetos JSON, preservar essa estrutura permite que o Architect trabalhe com o relacionamento original entre os dados, sem precisar converter objetos relacionados em várias coleções paralelas.

    Considere esta resposta:

    {
      "customers": [
        {
          "customerId": "C-1001",
          "orders": [
            {
              "orderId": "O-9001",
              "status": "OPEN"
            },
            {
              "orderId": "O-9002",
              "status": "CLOSED"
            }
          ]
        },
        {
          "customerId": "C-1002",
          "orders": [
            {
              "orderId": "O-9003",
              "status": "OPEN"
            }
          ]
        }
      ]
    }

    Uma alternativa seria criar coleções separadas:

    {
      "customerIds": [
        "C-1001",
        "C-1001",
        "C-1002"
      ],
      "orderIds": [
        "O-9001",
        "O-9002",
        "O-9003"
      ],
      "orderStatuses": [
        "OPEN",
        "CLOSED",
        "OPEN"
      ]
    }

    Embora essa estrutura possa ser consumida pelo Architect, ela cria uma dependência posicional entre as coleções:

    customerIds[0]
    orderIds[0]
    orderStatuses[0]

    Todos os índices precisam continuar representando exatamente o mesmo objeto.

    Qualquer diferença de ordenação, filtro ou quantidade pode quebrar silenciosamente essa associação.

    Por esse motivo, para APIs que retornam arrays de objetos, minha abordagem padrão é:

    1. Extrair o objeto ou array completo no translationMap.
    2. Transformar o resultado em uma JSON String no successTemplate.
    3. Retornar essa propriedade como string no output contract.
    4. Reconstruir o JSON no Architect com JsonParse.
    5. Percorrer os arrays com ToJsonCollection.

    Por exemplo, o translationMap pode preservar todo o retorno:

    {
      "payload": "$"
    }

    O successTemplate pode transformar o objeto em uma JSON String:

    {
      "payloadJson": "$esc.jsonEncode(${payload})"
    }

    O contrato de saída expõe apenas uma string:

    {
      "type": "object",
      "properties": {
        "payloadJson": {
          "type": "string"
        }
      }
    }

    No Architect, o conteúdo pode ser reconstruído:

    Task.ResponseJson = JsonParse(Task.PayloadJson)

    Depois, a coleção de clientes pode ser obtida com:

    Task.Customers = ToJsonCollection(Task.ResponseJson.customers)

    Cada cliente continua contendo seus próprios pedidos:

    Task.Customer.customerId
    Task.Customer.orders

    E os pedidos daquele cliente podem ser convertidos em outra coleção:

    Task.Orders = ToJsonCollection(Task.Customer.orders)

    Dessa forma, o relacionamento original permanece preservado:

    Customer
      └── Orders
            ├── Order 1
            └── Order 2

    Na minha experiência, os principais ganhos são:

    • Preservação da estrutura original da API.
    • Eliminação de coleções paralelas.
    • Menor dependência de índices relacionados.
    • Suporte mais simples a objetos opcionais.
    • Maior flexibilidade quando novos campos são adicionados.
    • Contrato de saída menor.
    • Menos alterações na Data Action quando a estrutura evolui.
    • Possibilidade de utilizar o mesmo payload em diferentes pontos do fluxo.

    Para objetos simples, concordo que essa abordagem não é necessária.

    Se a API retorna somente:

    {
      "customerId": "C-1001",
      "customerName": "John Doe",
      "isEligible": true
    }

    Faz sentido mapear diretamente essas propriedades no output contract.

    O uso da JSON String passa a ser mais relevante quando existe um array de objetos, especialmente quando esses objetos possuem outros arrays ou objetos aninhados.

    O único ponto negativo significativo que encontro nessa abordagem está na experiência de teste da Data Action.

    Quem testa a ação visualiza uma string JSON escapada, em vez de visualizar diretamente uma estrutura JSON formatada:

    "{\"customers\":[{\"customerId\":\"C-1001\"}]}"

    Isso torna a inspeção manual do resultado menos amigável.

    Ainda assim, considero essa uma limitação de visualização durante o teste, e não uma limitação arquitetural do processamento no Architect.


    I have been reviewing different approaches for handling API responses that contain arrays of JSON objects in Genesys Cloud Data Actions.

    One point I would like to make clear is that I do not consider sending this structure to Architect to introduce unnecessary complexity into the flow.

    In my view, it does the opposite.

    When an API returns an array of JSON objects, preserving that structure allows Architect to work with the original relationships between the data, without converting related objects into multiple parallel collections.

    Consider this response:

    {
      "customers": [
        {
          "customerId": "C-1001",
          "orders": [
            {
              "orderId": "O-9001",
              "status": "OPEN"
            },
            {
              "orderId": "O-9002",
              "status": "CLOSED"
            }
          ]
        },
        {
          "customerId": "C-1002",
          "orders": [
            {
              "orderId": "O-9003",
              "status": "OPEN"
            }
          ]
        }
      ]
    }

    One alternative would be to create separate collections:

    {
      "customerIds": [
        "C-1001",
        "C-1001",
        "C-1002"
      ],
      "orderIds": [
        "O-9001",
        "O-9002",
        "O-9003"
      ],
      "orderStatuses": [
        "OPEN",
        "CLOSED",
        "OPEN"
      ]
    }

    Although Architect can consume this structure, it creates a positional dependency between the collections:

    customerIds[0]
    orderIds[0]
    orderStatuses[0]

    Every index must continue to represent exactly the same object.

    Any difference in ordering, filtering, or collection size can silently break that relationship.

    For this reason, my standard approach for APIs that return arrays of objects is:

    1. Extract the complete object or array in the translationMap.
    2. Convert the result into a JSON string in the successTemplate.
    3. Return that property as a string in the output contract.
    4. Reconstruct the JSON in Architect with JsonParse.
    5. Iterate over the arrays using ToJsonCollection.

    For example, the translationMap can preserve the complete response:

    {
      "payload": "$"
    }

    The successTemplate can convert the object into a JSON string:

    {
      "payloadJson": "$esc.jsonEncode(${payload})"
    }

    The output contract exposes only a string:

    {
      "type": "object",
      "properties": {
        "payloadJson": {
          "type": "string"
        }
      }
    }

    In Architect, the content can be reconstructed:

    Task.ResponseJson = JsonParse(Task.PayloadJson)

    The customer collection can then be obtained with:

    Task.Customers = ToJsonCollection(Task.ResponseJson.customers)

    Each customer still contains its own orders:

    Task.Customer.customerId
    Task.Customer.orders

    The orders for that customer can be converted into another collection:

    Task.Orders = ToJsonCollection(Task.Customer.orders)

    The original relationship is therefore preserved:

    Customer
      └── Orders
            ├── Order 1
            └── Order 2

    In my experience, the main benefits are:

    • Preservation of the original API structure.
    • Elimination of parallel collections.
    • Less dependency on related indexes.
    • Simpler support for optional objects.
    • Greater flexibility when new fields are added.
    • A smaller output contract.
    • Fewer Data Action changes when the structure evolves.
    • The ability to reuse the same payload in different parts of the flow.

    For simple objects, I agree that this approach is unnecessary.

    If the API only returns:

    {
      "customerId": "C-1001",
      "customerName": "John Doe",
      "isEligible": true
    }

    It makes sense to map those properties directly in the output contract.

    The JSON string approach becomes more relevant when the response contains an array of objects, particularly when those objects contain additional nested arrays or objects.

    The only significant disadvantage I see is the Data Action testing experience.

    The person testing the action sees an escaped JSON string instead of a directly formatted JSON structure:

    "{\"customers\":[{\"customerId\":\"C-1001\"}]}"

    This makes manual inspection of the response less convenient.

    However, I consider this a test-time visualization limitation rather than an architectural limitation of processing the data in Architect.



    ------------------------------
    Matheus Mendonca
    ------------------------------



  • 8.  RE: Handling nested arrays in Data Action responses

    Posted 3 days ago

    Ao configurar respostas de Data Actions no Genesys Cloud, é comum tratar translationMap e successTemplate como se ambos executassem a mesma função.

    Na prática, eles possuem responsabilidades diferentes.

    O translationMap utiliza expressões JSONPath para localizar e extrair dados da resposta original.

    O successTemplate, por outro lado, utiliza Velocity Template Language para construir a resposta final que deverá respeitar o success schema.

    Essa separação é importante:

    Raw response
         ↓
    Translation Map - JSONPath
         ↓
    Aliases e valores extraídos
         ↓
    Success Template - Velocity
         ↓
    Resolved response

    Considere a seguinte resposta:

    {
      "customers": [
        {
          "customerId": "C-1001",
          "name": "Customer \"One\"",
          "orders": [
            {
              "orderId": "O-9001"
            }
          ]
        }
      ],
      "metadata": {
        "requestId": "REQ-10001"
      }
    }

    O translationMap deve ser utilizado para localizar os dados:

    {
      "customers": "$.customers",
      "requestId": "$.metadata.requestId"
    }

    Aqui não estão sendo executadas macros Velocity.

    As expressões:

    $.customers
    $.metadata.requestId

    são JSONPath.

    O resultado é associado aos aliases:

    customers
    requestId

    Esses aliases ficam disponíveis no successTemplate.

    Criando uma JSON String com segurança

    Quando customers contém um array de objetos, podemos utilizar:

    {
      "payloadJson": "$esc.jsonEncode(${customers})",
      "requestId": "$esc.jsonEncode(${requestId})"
    }

    O $esc.jsonEncode() escapa aspas, quebras de linha, barras e outros caracteres reservados do JSON.

    Sem esse tratamento, um valor como:

    Customer "One"

    poderia gerar uma resposta inválida ao ser inserido diretamente dentro de uma string JSON.

    Formal notation e silent formal notation

    Velocity permite duas formas importantes de referenciar uma variável.

    Formal notation:

    ${requestId}

    Caso a variável seja nula, a referência pode aparecer literalmente na saída.

    Silent formal notation:

    $!{requestId}

    Caso a variável seja nula, o resultado será uma string vazia.

    Essa diferença é especialmente importante quando o payload contém propriedades opcionais.

    Por exemplo:

    {
      "requestId": "$esc.jsonEncode($!{requestId})"
    }

    Entretanto, é necessário avaliar se uma string vazia representa corretamente a ausência do valor.

    Dependendo do contrato, talvez seja mais adequado retornar null, omitir a propriedade ou definir um valor padrão.

    Translation Map Defaults

    Também é possível configurar translationMapDefaults:

    {
      "customers": "[]",
      "requestId": ""
    }

    Esses valores são utilizados quando a expressão JSONPath não consegue resolver um resultado.

    Um detalhe importante é que o default não substitui valores que foram resolvidos explicitamente como null.

    Existe diferença entre:

    Propriedade inexistente

    e:

    {
      "requestId": null
    }

    Por isso, os cenários com propriedade ausente, valor nulo, string vazia e array vazio precisam ser testados separadamente.

    Extraindo o primeiro elemento

    Quando o requisito realmente é obter somente o primeiro item, o Genesys Cloud disponibiliza:

    ${successTemplateUtils.firstFromArray("${customers}", "{}")}

    Essa macro retorna o primeiro elemento de um array JSON.

    Caso o array esteja vazio, o segundo parâmetro pode definir um retorno padrão.

    Ainda assim, eu utilizaria essa macro somente quando a regra de negócio realmente determinar que apenas o primeiro elemento é relevante.

    Ela não deve ser usada para contornar a necessidade de tratar corretamente uma coleção completa.

    Chaves dinâmicas

    Outro caso interessante é uma resposta com propriedades dinâmicas:

    {
      "customers": {
        "C-1001": {
          "status": "ACTIVE"
        },
        "C-1002": {
          "status": "INACTIVE"
        }
      }
    }

    Nesse cenário, a chave representa o próprio identificador do cliente.

    A macro:

    successTemplateUtils.moveKeysIntoArrayOfObjects

    pode mover essas chaves para dentro dos objetos e produzir uma estrutura mais adequada para consumo posterior.

    O resultado poderia ser semelhante a:

    [
      {
        "customerId": "C-1001",
        "status": "ACTIVE"
      },
      {
        "customerId": "C-1002",
        "status": "INACTIVE"
      }
    ]

    Ainda assim, caso o resultado seja um array de objetos, eu continuaria retornando esse array como JSON String para reconstruí-lo no Architect.

    Minha divisão de responsabilidades é:

    Translation Map

    • Localizar dados com JSONPath.
    • Extrair objetos, arrays ou valores.
    • Criar aliases.
    • Definir defaults para caminhos não resolvidos.

    Success Template com Velocity

    • Construir a resposta final.
    • Escapar strings.
    • Codificar objetos como JSON String.
    • Aplicar tratamentos de ausência de valor.
    • Extrair um primeiro elemento quando houver regra para isso.
    • Transformar estruturas com chaves dinâmicas.
    • Garantir compatibilidade com o output contract.

    Quais macros Velocity vocês mais utilizam nas Data Actions?

    Existem outros padrões que vocês adotam para evitar respostas inválidas, principalmente quando os dados contêm caracteres especiais, valores nulos ou propriedades dinâmicas?


    When configuring Data Action responses in Genesys Cloud, it is common to treat the translationMap and the successTemplate as if both performed the same function.

    In practice, they have different responsibilities.

    The translationMap uses JSONPath expressions to locate and extract data from the raw response.

    The successTemplate, on the other hand, uses the Velocity Template Language to build the final response that must conform to the success schema.

    This separation is important:

    Raw response
         ↓
    Translation Map - JSONPath
         ↓
    Extracted aliases and values
         ↓
    Success Template - Velocity
         ↓
    Resolved response

    Consider the following response:

    {
      "customers": [
        {
          "customerId": "C-1001",
          "name": "Customer \"One\"",
          "orders": [
            {
              "orderId": "O-9001"
            }
          ]
        }
      ],
      "metadata": {
        "requestId": "REQ-10001"
      }
    }

    The translationMap should be used to locate the data:

    {
      "customers": "$.customers",
      "requestId": "$.metadata.requestId"
    }

    No Velocity macros are being executed here.

    The expressions:

    $.customers
    $.metadata.requestId

    are JSONPath expressions.

    Their results are assigned to the following aliases:

    customers
    requestId

    These aliases then become available to the successTemplate.

    Safely creating a JSON string

    When customers contains an array of objects, we can use:

    {
      "payloadJson": "$esc.jsonEncode(${customers})",
      "requestId": "$esc.jsonEncode(${requestId})"
    }

    The $esc.jsonEncode() macro escapes quotes, line breaks, backslashes, and other JSON-reserved characters.

    Without this treatment, a value such as:

    Customer "One"

    could produce an invalid response when inserted directly into a JSON string.

    Formal notation and silent formal notation

    Velocity provides two important ways to reference a variable.

    Formal notation:

    ${requestId}

    If the variable is null, the variable reference itself can appear in the output.

    Silent formal notation:

    $!{requestId}

    If the variable is null, the result is an empty string.

    This difference is especially relevant when the payload contains optional properties.

    For example:

    {
      "requestId": "$esc.jsonEncode($!{requestId})"
    }

    However, it is still necessary to determine whether an empty string correctly represents the missing value.

    Depending on the contract, returning null, omitting the property, or defining a default value may be more appropriate.

    Translation Map Defaults

    It is also possible to configure translationMapDefaults:

    {
      "customers": "[]",
      "requestId": ""
    }

    These values are used when a JSONPath expression cannot resolve a result.

    An important detail is that the default does not replace values explicitly resolved as null.

    There is a difference between:

    Missing property

    and:

    {
      "requestId": null
    }

    For this reason, missing properties, null values, empty strings, and empty arrays should be tested as separate scenarios.

    Extracting the first element

    When the requirement is specifically to obtain only the first item, Genesys Cloud provides:

    ${successTemplateUtils.firstFromArray("${customers}", "{}")}

    This macro returns the first element from a JSON array.

    If the array is empty, the second parameter can define a default response.

    Even so, I would only use this macro when the business rule actually determines that only the first element is relevant.

    It should not be used to avoid correctly handling a complete collection.

    Dynamic keys

    Another interesting scenario is a response containing dynamic properties:

    {
      "customers": {
        "C-1001": {
          "status": "ACTIVE"
        },
        "C-1002": {
          "status": "INACTIVE"
        }
      }
    }

    In this structure, the property key represents the customer identifier.

    The following macro can move these keys into the objects:

    successTemplateUtils.moveKeysIntoArrayOfObjects

    The resulting structure could be similar to:

    [
      {
        "customerId": "C-1001",
        "status": "ACTIVE"
      },
      {
        "customerId": "C-1002",
        "status": "INACTIVE"
      }
    ]

    Even in this case, if the result is an array of objects, I would still return it as a JSON string and reconstruct it in Architect.

    My preferred division of responsibilities is:

    Translation Map

    • Locate data with JSONPath.
    • Extract objects, arrays, or individual values.
    • Create aliases.
    • Define defaults for unresolved paths.

    Success Template with Velocity

    • Build the final response.
    • Escape strings.
    • Encode objects as JSON strings.
    • Handle missing values.
    • Extract the first element when required.
    • Transform structures containing dynamic keys.
    • Ensure compatibility with the output contract.

    Which Velocity macros do you use most frequently in Data Actions?

    Do you follow any other patterns to prevent invalid responses, particularly when the data contains special characters, null values, or dynamic properties?



    ------------------------------
    Matheus Mendonca
    ------------------------------



  • 9.  RE: Handling nested arrays in Data Action responses

    Posted 3 days ago

    Genesys Cloud Function Data Actions adicionam uma possibilidade interessante à plataforma: executar código customizado para tratar respostas, aplicar regras de negócio, realizar transformações e integrar diferentes serviços.

    Entretanto, não considero que uma Function deva ser automaticamente a solução padrão sempre que uma Data Action recebe um JSON complexo.

    Antes de mover o processamento para uma Function, acredito que seja necessário avaliar não apenas se o código funciona, mas também como essa solução será mantida, migrada, documentada e transferida para outro time ou parceiro no futuro.

    1. Timeout máximo de 15 segundos

    Uma Function pode ser configurada com timeout entre 1 e 15 segundos.

    Caso ultrapasse esse período, sua execução é interrompida.

    Esse limite pode ser suficiente para transformações locais, porém se torna relevante quando a Function precisa:

    • Consultar uma API externa.
    • Realizar múltiplas chamadas sequenciais.
    • Aguardar sistemas legados.
    • Processar payloads maiores.
    • Aplicar retries.
    • Consultar diferentes serviços para consolidar uma resposta.

    Por exemplo:

    API A: 4 segundos
    API B: 5 segundos
    API C: 4 segundos
    Processamento: 2 segundos
    Total: 15 segundos

    Nesse cenário, qualquer oscilação de rede pode provocar o timeout.

    Além disso, implementar retries dentro da Function pode consumir rapidamente toda a janela disponível.

    Isso significa que o desenho precisa considerar:

    • Tempo de conexão.
    • Tempo de resposta externo.
    • Processamento local.
    • Eventuais retries.
    • Margem de segurança.
    • Tempo total percebido pelo fluxo.

    Para integrações com comportamento variável, 15 segundos pode ser um limite bastante restritivo.

    2. O arquivo ZIP não pode ser baixado

    Para publicar uma Function, o código deve ser enviado em um arquivo ZIP.

    Entretanto, depois do upload, esse ZIP não pode ser baixado novamente pelo Genesys Cloud.

    Além disso, o upload de um novo ZIP substitui o arquivo anterior.

    Esse comportamento cria uma regra operacional importante:

    O Genesys Cloud não deve ser tratado como o repositório do código-fonte da Function.

    O código precisa estar armazenado externamente em um repositório versionado, acompanhado de:

    • Código-fonte.
    • Dependências.
    • Arquivo de lock.
    • Instruções de build.
    • Procedimento de empacotamento.
    • Variáveis necessárias.
    • Histórico de versões.
    • Procedimento de rollback.
    • ZIP correspondente à versão implantada.

    Sem isso, a organização possui uma Function em execução, mas pode não possuir os arquivos necessários para reproduzi-la.

    3. Function Data Actions não suportam importação, cópia ou exportação

    Data Actions tradicionais podem ter seus contratos e configurações exportados e importados entre organizações.

    Function Data Actions não oferecem suporte às opções de:

    • Import.
    • Copy.
    • Export.

    Isso tem um impacto direto em projetos que precisam migrar configurações entre ambientes ou organizações.

    Em uma Data Action tradicional, é possível exportar o JSON da configuração, ajustar referências e importar a ação no ambiente de destino.

    Com Functions, o processo precisa ser reconstruído utilizando artefatos externos.

    É necessário transportar separadamente:

    • Código-fonte.
    • ZIP compilado.
    • Contratos de entrada e saída.
    • Configuração da Function.
    • Runtime.
    • Handler.
    • Timeout.
    • Dependências.
    • Documentação de implantação.
    • Configuração da integração.
    • Permissões.
    • Referências utilizadas pelos fluxos.

    4. Risco em migrações entre parceiros Genesys

    Esse ponto se torna ainda mais relevante durante uma mudança de parceiro responsável pela operação do Genesys Cloud.

    Imagine o seguinte cenário:

    1. O parceiro anterior criou uma Function.
    2. O código foi enviado diretamente pelo computador de um desenvolvedor.
    3. Não existe um repositório acessível ao cliente.
    4. O ZIP original não foi incluído na documentação de transição.
    5. O novo parceiro recebe apenas acesso à organização Genesys Cloud.

    Nesse caso, o novo parceiro consegue visualizar que a Function existe e pode analisar seus contratos e configurações disponíveis.

    Porém, não consegue baixar o ZIP implantado para recuperar o código.

    Dependendo da documentação entregue, pode ser necessário reconstruir a solução do zero.

    Por isso, em projetos conduzidos por parceiros, considero essencial definir contratualmente quem é responsável por manter e entregar:

    • Repositório do código.
    • Histórico de versões.
    • Artefatos de build.
    • ZIP implantado.
    • Documentação técnica.
    • Instruções de implantação.
    • Credenciais e variáveis necessárias.
    • Evidências de testes.
    • Plano de rollback.

    Sem essa governança, a Function pode criar uma dependência operacional do parceiro que realizou a implementação original.

    5. Documentação ainda limitada para o ciclo de vida completo

    A documentação disponível permite compreender os requisitos básicos para configurar e enviar uma Function.

    Entretanto, na minha percepção, ainda existem poucas orientações detalhadas sobre assuntos como:

    • Estratégia de versionamento.
    • Recuperação de artefatos.
    • CI/CD.
    • Rollback.
    • Migração entre organizações.
    • Migração entre parceiros.
    • Comparação entre versões implantadas.
    • Gestão de dependências.
    • Observabilidade do código.

    Esses temas acabam ficando sob responsabilidade da arquitetura definida pelo cliente ou pelo parceiro.

    6. Maior quantidade de componentes para administrar

    Uma solução baseada em Function não contém apenas uma Data Action.

    Ela também envolve:

    • Código Node.js.
    • Runtime suportado.
    • Dependências.
    • ZIP de implantação.
    • Handler.
    • Timeout.
    • Repositório.
    • Pipeline ou processo manual de publicação.
    • Monitoramento.
    • Logs.
    • Documentação.
    • Processo de rollback.

    Isso aumenta a capacidade técnica da solução, mas também aumenta sua superfície operacional.

    7. O código pode esconder regras de negócio

    Quando a transformação está no translationMap, no successTemplate ou no Architect, uma parte relevante da lógica permanece visível dentro das configurações do Genesys Cloud.

    Quando essa lógica é movida para uma Function, ela passa a existir dentro de um arquivo externo.

    Isso pode dificultar o entendimento para profissionais que possuem acesso à organização, mas não ao repositório.

    Por exemplo, a Function pode:

    • Descartar determinados itens.
    • Reordenar arrays.
    • Selecionar apenas um resultado.
    • Alterar valores nulos.
    • Aplicar regras de elegibilidade.
    • Chamar APIs adicionais.
    • Substituir campos.
    • Consolidar dados.

    Se essas decisões não estiverem documentadas, o comportamento observado no Architect pode não ser explicado apenas pela configuração da Data Action.

    Quando eu utilizaria uma Function?

    Eu consideraria Function Data Actions quando a necessidade realmente exigisse:

    • Múltiplas chamadas de API do Genesys Cloud principalmente.
    • Algoritmos que não podem ser representados adequadamente em Velocity.
    • Transformações complexas.
    • Validações criptográficas.
    • Agregações avançadas.
    • Regras dinâmicas.
    • Conversões específicas de protocolo ou formato.

    Para o tratamento de arrays de objetos JSON, entretanto, minha primeira opção continuaria sendo:

    API response
         ↓
    Translation Map
         ↓
    JSON String no Success Template
         ↓
    JsonParse no Architect

    Essa abordagem mantém a transformação dentro das capacidades nativas da Data Action e do Architect, reduzindo a quantidade de artefatos externos que precisam ser versionados e transferidos.

    Não considero Function Data Actions uma solução ruim.

    Considero uma solução poderosa que precisa ser adotada com governança, versionamento, documentação e avaliação clara do impacto de portabilidade.

    Como vocês estão administrando o código-fonte das Functions?

    Em migrações entre organizações ou parceiros, quais artefatos vocês consideram obrigatórios no processo de transição?


    Genesys Cloud Function Data Actions introduce an interesting platform capability: executing custom code to process responses, apply business rules, transform data, and integrate different services.

    However, I do not believe that a Function should automatically become the default solution whenever a Data Action receives complex JSON.

    Before moving processing into a Function, I believe it is necessary to evaluate not only whether the code works, but also how the solution will be maintained, migrated, documented, and transferred to another team or partner in the future.

    1. Maximum timeout of 15 seconds

    A Function can be configured with a timeout between 1 and 15 seconds.

    If it exceeds that period, its execution is stopped.

    This limit may be sufficient for local transformations, but it becomes relevant when the Function must:

    • Call an external API.
    • Perform multiple sequential calls.
    • Wait for legacy systems.
    • Process larger payloads.
    • Apply retries.
    • Query different services to consolidate a response.

    For example:

    API A: 4 seconds
    API B: 5 seconds
    API C: 4 seconds
    Processing: 2 seconds
    Total: 15 seconds

    In this scenario, any network variation can cause a timeout.

    Implementing retries inside the Function can also consume the entire available execution window very quickly.

    The design must therefore account for:

    • Connection time.
    • External response time.
    • Local processing.
    • Potential retries.
    • Safety margin.
    • Total time perceived by the flow.

    For integrations with variable response times, 15 seconds can be a restrictive limit.

    2. The ZIP file cannot be downloaded

    To publish a Function, the custom code must be uploaded as a ZIP file.

    However, after the upload, that ZIP file cannot be downloaded again from Genesys Cloud.

    Uploading a new ZIP also replaces the existing file.

    This creates an important operational rule:

    Genesys Cloud should not be treated as the source-code repository for the Function.

    The code must be stored externally in a version-controlled repository, together with:

    • Source code.
    • Dependencies.
    • Lock file.
    • Build instructions.
    • Packaging procedure.
    • Required variables.
    • Version history.
    • Rollback procedure.
    • The ZIP corresponding to the deployed version.

    Without these artifacts, an organization may have a running Function but no way to reproduce the deployed package.

    3. Function Data Actions do not support import, copy, or export

    Traditional Data Actions can have their contracts and configurations exported and imported between organizations.

    Function Data Actions do not support:

    • Import.
    • Copy.
    • Export.

    This directly affects projects that need to migrate configurations between environments or organizations.

    With a traditional Data Action, it is possible to export the configuration JSON, update references, and import the action into the destination environment.

    With Functions, the process must be reconstructed using externally maintained artifacts.

    The migration package must separately include:

    • Source code.
    • Compiled ZIP package.
    • Input and output contracts.
    • Function configuration.
    • Runtime.
    • Handler.
    • Timeout.
    • Dependencies.
    • Deployment documentation.
    • Integration configuration.
    • Permissions.
    • References used by Architect flows.

    4. Risk during migrations between Genesys partners

    This becomes even more relevant when responsibility for the Genesys Cloud environment moves from one partner to another.

    Consider the following scenario:

    1. The previous partner created a Function.
    2. The code was uploaded directly from a developer's computer.
    3. There is no repository accessible to the customer.
    4. The original ZIP was not included in the transition documentation.
    5. The new partner only receives access to the Genesys Cloud organization.

    The new partner can identify that the Function exists and inspect the contracts and configurations that remain visible.

    However, the new partner cannot download the deployed ZIP to recover the code.

    Depending on the documentation provided, the solution may have to be rebuilt from scratch.

    For this reason, in partner-led projects, I consider it essential to define who is responsible for maintaining and delivering:

    • The source-code repository.
    • Version history.
    • Build artifacts.
    • The deployed ZIP package.
    • Technical documentation.
    • Deployment instructions.
    • Required credentials and variables.
    • Test evidence.
    • Rollback procedures.

    Without this governance, the Function can create an operational dependency on the partner that originally implemented it.

    5. Limited documentation for the complete lifecycle

    The available documentation explains the basic requirements for configuring and uploading a Function.

    However, in my experience, there is still limited detailed guidance on topics such as:

    • Versioning strategy.
    • Artifact recovery.
    • CI/CD.
    • Rollback.
    • Migration between organizations.
    • Migration between partners.
    • Comparison between deployed versions.
    • Dependency management.
    • Code observability.
    • Logging standards.

    These areas are therefore left to the architecture defined by the customer or implementation partner.

    6. More components to manage

    A Function-based solution does not consist only of a Data Action.

    It also includes:

    • Node.js code.
    • Supported runtime.
    • Dependencies.
    • Deployment ZIP.
    • Handler.
    • Timeout.
    • Repository.
    • Deployment pipeline or manual process.
    • Monitoring.
    • Logs.
    • Documentation.
    • Rollback procedure.

    This increases the technical capability of the solution, but also increases its operational surface.

    7. Business rules can become hidden inside the code

    When transformations are implemented in the translationMap, successTemplate, or Architect, a significant part of the logic remains visible within Genesys Cloud configuration.

    When that logic is moved into a Function, it exists inside an external code package.

    This can make the behavior more difficult to understand for professionals who have access to the Genesys Cloud organization but not to the repository.

    For example, the Function may:

    • Discard specific items.
    • Reorder arrays.
    • Select only one result.
    • Replace null values.
    • Apply eligibility rules.
    • Call additional APIs.
    • Replace properties.
    • Consolidate data.

    If these decisions are not documented, the behavior observed in Architect may not be explainable from the Data Action configuration alone.

    When would I use a Function?

    I would consider Function Data Actions when the requirement genuinely involves:

    • Multiple API calls.
    • Algorithms that cannot be adequately represented with Velocity.
    • Complex transformations.
    • Cryptographic validation.
    • Advanced aggregation.
    • Dynamic rules.
    • Specific protocol or format conversions.

    For handling arrays of JSON objects, however, my first option would remain:

    API response
         ↓
    Translation Map
         ↓
    JSON string in the Success Template
         ↓
    JsonParse in Architect

    This approach keeps the transformation within the native capabilities of Data Actions and Architect, reducing the number of external artifacts that must be versioned and transferred.

    I do not consider Function Data Actions to be a bad solution.

    I consider them a powerful solution that must be adopted with governance, version control, documentation, and a clear evaluation of portability impacts.

    How are you managing the source code for your Functions?

    During migrations between organizations or partners, which artifacts do you consider mandatory in the transition process?



    ------------------------------
    Matheus Mendonca
    ------------------------------



  • 10.  RE: Handling nested arrays in Data Action responses

    Posted an hour ago

    We have many more options available today, and Genesys has improved a lot compared to the early days.
    Taking advantage of this topic, for those using Genesys Cloud Functions in production, how do you manage Node.js runtime upgrades and deprecations?



    ------------------------------
    Att,
    Breno Canyggia Ferreira Marreco
    ------------------------------