JSON – O que é e como usar em AdvPL – Parte 03 – zJsonKit

Introdução

Nos posts anteriores — JSON – O que é e como usar em AdvPLJSON – O que é e como usar em AdvPL – Parte 02 — demos uma boa olhada de “o que é” e “como funciona” um JSON, e como ler/manipular seu conteúdo usando a classe TJsonObject() do AdvPL. Agora, vamos ver por dentro uma ferramenta visual feita em AdvPL para abrir um JSON a partir de uma string ou de um arquivo, que gera uma lista com as propriedades e conteúdos do objeto, da forma como você pode endereçar esses dados em AdvPL!

zJsonKit

A aplicação está no Github do Blog — fonte zJsonKit.prw — e não depende do projeto da ZLIB — pode compilar em qualquer RPO, desde que use um TOTVS Application Server que já tenha a classe JsonObject() implementada. Antes de ver os fontes, vamos ver como usamos a ferramenta.

Após baixar o fonte, acrescentar em um projeto AdvPL, e compilar o fonte — TDS, IDE, VScode — abrimos um SmartClient e executamos a função “U_ZJSONKIT”, e veremos a tela abaixo:

 

jSonKit 01

A interface do aplicativo fornece 3 abas, mostradas em destaque na parte superior : “JSon String” , “JSon File” e “Resultado”.  Usamos a primeira aba para digitar e/ou “colar” uma string JSON na interface, e fazer o “Parse” da string. Ao clicar no botão de “Parser” o programa vai criar um objeto JSON em AdvPL, e gerar na aba de “Resultado” uma lista dos valores encontrados dentro do JSON, mostrando como cada valor foi recuperado de dentro do Objeto 😀 Se você tem um arquivo com conteúdo JSON, você pode informar o arquivo com path completo usando a segunda aba. Após informar o arquivo, clique em “Parser”.

Por exemplo, vamos colar a seguinte string na primeira tela:{

{
  "Titulo" : "Exemplo de JSON Parser",
  "Id" : 1,
  "Plataformas" : [ "Windows" , "Linux" ],
  "Objeto JSON" : { "Tipo" : "String" , "Formatado" : true }
}

Ao clicar no botão “Parser”, será mostrada a mensagem:

jSonKit 02

Automaticamente o foco será transferido para a última aba — após você clicar no “Ok”:

jSonKit 03

Por padrão, são mostrados todos os dados identificados dentro do JSON, e como eles “chegaram” no AdvPL. Para mudar a visualização dos dados processados, você pode marcar a opção “Somente Estrutura” por exemplo, e depois clicar no botão  “Atualizar”:

jSonKit 04

Repare que agora somente foram mostrados os Arrays e os Objetos JSON dentro do Objeto principal. Isso facilita ver a estrutura dos dados dentro de um JSON. Vou agora usar um arquivo de testes com mais dados, vamos ver como fica — basta escolher uma das Abas de Parser, entrar com o novo JSON, e rodar o “Parser” novamente.

jSonKit 05

O programa mostra o resultado do ultimo parser realizado. Ao usar o parser informando um arquivo, o conteúdo do arquivo é carregado apenas para executar o Parser, ele não é trazido para a aba “JSON String”.

A configuração “Ignorar NULL” quando habilitada deixa de mostrar na tela os valores “null” que foram declarados no JSON, após alterar qualquer uma das configurações de visualização, basta clicar no botão “Atualizar” para ele aplicar o filtro e trazer os dados na tela.

O botão “Copiar” pega o texto todo mostrado na aba “Resultados” e copia para a área de transferência do Windows — Já que o resultado é mostrado em uma “lista”. Com isso você pode abrir um Bloco de notas ou outro editor  e “colar” as informações que mostram como cada nó / dado do JSON foram obtidas no AdvPL. 😀

Vamos ao fonte

Se ignorarmos os comentários, a mágica toda é feita em menos de 300 linhas, usando apenas as funções básicas da linguagem AdvPL:

#include 'protheus.ch'

/* ===================================================================================
Funcao U_ZJSONKIT()
Autor Julio Wittwer
Data 01/12/2019
Descrição Interface para identificar informações de Objeto JSON

Transforma uma String JSON em um objeto AdvPL, e monta os nodes 
com o caminho para obtenção das informações . Usa uma string fornecida 
na hora ou o conteudo de um arquivo indicado a partir do RootPath 
ou na máquina cliente ( onde está o SmartClient ) 
==================================================================================== */

User Function ZJSONKIT()
Local oDlg
Local oGetJSON
Local cJSON := ''
Local oJSON 
Local cJSONFile := space(80) 
Local nOpList
Local aNodeList := {''}
Local oListResult 
Local oFont
Local lIgnoreNull := .F. 
Local lOnlyStruct := .F. 
Local aResult := {}

// Define Formato de data DD/MM/AAAA
SET DATE BRITISH
SET CENTURY ON

// Fonte Default tamanho 10, Monoespaçada
oFont := TFont():New('Courier new',,-12,.T.)
SETDEFFONT(oFont)

// Cria o objeto JSON 
oJSON := JsonObject():New()

// Cria uma caixa de diálogo com área util de 800x600 PIXELs
DEFINE DIALOG oDlg TITLE "AdvPL JSON Kit" FROM 0,0 TO 600,800 PIXEL

@ 0,0 FOLDER oFolders PROMPTS "JSON String","JSON File","Resultado" OF oDlg PIXEL
oFolders:ALIGN := CONTROL_ALIGN_ALLCLIENT

// Folder 1 . Parser da String informada

@ 10,10 SAY "Informe a String JSON para avaliar" OF oFolders:aDialogs[1] PIXEL
@ 20,10 GET oGetJSON VAR cJSON MULTILINE SIZE 380, 200 of oFolders:aDialogs[1] PIXEL

@ 230,10 BUTTON oBtmParse PROMPT "&Parser" ;
  ACTION (DoPArser(oDlg,oJSON,cJSON,aResult),ShowResult(oFolders,oListResult,aResult,lIgnoreNull,lOnlyStruct)) ;
  SIZE 040, 013 OF oFolders:aDialogs[1] PIXEL

@ 230,350 BUTTON oBtmParse PROMPT "Sai&r" ;
  ACTION (oDlg:End()) ;
  SIZE 040, 013 OF oFolders:aDialogs[1] PIXEL

// Folder 2 . Parser do Arquivo JSON informado

@ 10,10 SAY "Informe o arquivo JSON para avaliar" OF oFolders:aDialogs[2] PIXEL
@ 20,10 GET oGetFile VAR cJSONFile SIZE 380, 13 of oFolders:aDialogs[2] PIXEL

@ 230,10 BUTTON oBtnParseF PROMPT "&Parser" ;
  ACTION (DoParserFile(oDlg,oJSON,cJSONFile,aResult),ShowResult(oFolders,oListResult,aResult,lIgnoreNull,lOnlyStruct)) ;
  SIZE 040, 013 of oFolders:aDialogs[2] PIXEL

@ 230,350 BUTTON oBtmParse PROMPT "Sai&r" ;
  ACTION (oDlg:End()) ;
  SIZE 040, 013 of oFolders:aDialogs[2] PIXEL

// Folder 3 - ListBox com o resultado

@ 230,60 CHECKBOX oChkBox1 VAR lIgnoreNull PROMPT "Ignorar NULL" SIZE 80,12 OF oFolders:aDialogs[3] PIXEL

@ 230,120 CHECKBOX oChkBox2 VAR lOnlyStruct PROMPT "Somente Estrutura" SIZE 80,12 OF oFolders:aDialogs[3] PIXEL

@ 230,210 BUTTON oBtnUpd PROMPT "&Atualizar" ;
  ACTION (ShowResult(oFolders,oListResult,aResult,lIgnoreNull,lOnlyStruct)) ;
  SIZE 040, 013 of oFolders:aDialogs[3] PIXEL

@ 230,280 BUTTON oBtnExp PROMPT "&Copiar" ;
  ACTION (ToClipboard(oListResult)) ;
  SIZE 040, 013 of oFolders:aDialogs[3] PIXEL

@ 230,350 BUTTON oBtmParse PROMPT "Sai&r" ;
  ACTION (oDlg:End()) ;
  SIZE 040, 013 of oFolders:aDialogs[3] PIXEL

@ 20,10 LISTBOX oListResult VAR nOpList;
  ITEMS aNodeList ;
  SIZE 380, 200;
  OF oFolders:aDialogs[3] PIXEL

// Listbox ocupa o frame inteiro 
// oListResult:ALIGN := CONTROL_ALIGN_ALLCLIENT

ACTIVATE DIALOG oDlg CENTER

Return

// ---------------------------------------------------------------------------- 
// Realiza o parser da string informada na interface

STATIC Function DoPArser(oDlg,oJSON,cJSON,aResult)
Local nTimer 
Local cMsg

If Empty(cJSON)
  MsgInfo('Infome um JSON para executar o Parser')
  Return
Endif

// Limpa o resultado 
aSize(aResult,0)

nTimer := seconds()
cMsg := oJSON:fromJson(cJSON) 
nTimer := seconds()-nTimer

If !empty(cMsg)
  If len(cMsg)>512
    cMsg := left(cMsg,512)
  Endif
  MsgStop(cMsg,"JSON Parse Error") 
Else
  cMsg := "Parser executado em "+str(nTimer,12,2)+' s.'
  BuildResult(oJSON,aResult,"",left(cJson,1)=='[')
  MsgInfo(cMsg,"Parse OK") 
  // Ordena o resutado alfabeticamente
  aSort(aResult)
Endif

Return

// ---------------------------------------------------------------------------- 
// Faz o parser de um arquivo JSON

STATIC Function DoParserFile(oDlg,oJSON,cJSONFile,aResult)
Local nTimer 
Local cMsg 
Local cJSonStr := ''
Local nH
Local nTam

cJSONFile := alltrim(cJSONFile)

If Empty(cJSONFile)
  MsgStop('Infome um arquivo JSON para executar o Parser')
  Return
Endif

If !file(cJSONFile)
  MsgStop('Arquivo ['+cJSONFile+'] não encontrado.')
  Return
Endif

// Limpa o resultado 
aSize(aResult,0)

// Carrega o conteudo do arquivo para a mémória 
nH := Fopen(cJsonFile)
nTam := fSeek(nh,0,2)
fSeek(nH,0)
fRead(nH,@cJSonStr,nTam)
fClose(nH)

// Popula o objeto JSON a partir da string
nTimer := seconds()
cMsg := oJSON:FromJson(cJSonStr)
nTimer := seconds()-nTimer

If !empty(cMsg)
  IF len(cMsg) > 512
    cMsg := left(cMsg,512)
  Endif
  MsgStop(cMsg,"JSON PARSE ERROR") 
Else
  cMsg := "Parser executado em "+str(nTimer,12,2)+' s.'
  BuildResult( oJSON , aResult,"",left(cJSonStr,1)=='[')
  MsgInfo(cMsg,"JSON Parse OK") 
  // Ordena o resutado alfabeticamente
  aSort(aResult)
Endif

Return

// ---------------------------------------------------------------------------- 
// Monta um Array de resultados com os dados obtidos do Objeto 
// Gera e varre o Objeto JSON recursivamente, gerando os caminhos de acesso 
// aos dados e estrutura de objetos do JSON

STATIC Function BuildResult(oJSON,aResult,cPath,lBeginArray)
Local cRow 
Local aNames , nI 
Local lIsJson := .F.
Local lIsArray := .F. 
Local xValue

DEFAULT aResult := {}
DEFAULT cPath := '' 
DEFAULT lBeginArray := .F.

If !empty(cPath)
  cPath += ']['
Endif

IF valtype(oJson) == 'A' .or. ( valtype(oJson) == 'J' .and. lBeginArray )

  // O Objeto é um array, varre todos os elementos
  For nI := 1 to len(oJson)
    xValue := oJson[nI] 
    lIsJson := ( valtype(xValue) = 'J' )
    lIsArray := ( valtype(xValue) = 'A' )
    cRow := 'oJson['+cPath+cValToChar(nI)+'] // Tipo ['+valtype(xValue)+']'
    If lIsJson .or. lIsArray
      If lIsArray
        cRow += ' Elementos ['+cValToChar(len(xValue))+']'
      Endif
      BuildResult(xValue,aResult,cPath+str(nI,4),.F.)
    ElseIf valtype(xValue) != 'U'
      cRow += ' Conteudo ['+cValToChar(xValue)+']'
      aadd(aResult,cRow)
    Endif
  Next

ElseIf valtype(oJson) == 'J'

  // Identifica a lista de propriedades do objeto JSOB
  // Varre a lista avaliando todas as propriedades
  aNames := oJson:GetNames()
  For nI := 1 to len(aNames)
    xValue := oJson[aNames[nI]]
    lIsJson := ( valtype(xValue) = 'J' )
    lIsArray := ( valtype(xValue) = 'A' )
    cRow := 'oJson['+cPath+'"'+aNames[nI]+'"] // Tipo ['+valtype(xValue)+']'
    If lIsJson .or. lIsArray
      If lIsArray
        cRow += ' Elementos ['+cValToChar(len(xValue))+']'
      Endif
      aadd(aResult,cRow)
      BuildResult(xValue,aResult,cPath+'"'+aNames[nI]+'"',.F.)
    ElseIf valtype(xValue) != 'U'
      cRow += ' Conteudo ['+cValToChar(xValue)+']'
      aadd(aResult,cRow)
    Endif
  Next

Endif

Return


// ---------------------------------------------------------------------------- 
// Mosrta o resultado considerando os parametros de filtro

STATIC Function ShowResult(oFolders,oListResult,aResult,lIgnoreNull,lOnlyStruct)
Local aShowResult := {}
Local nI , cRow

If len(aResult) > 0 
  For nI := 1 to len(aResult)
    cRow := aResult[nI]
    IF lIgnoreNull .and. "Tipo [U]" $ cRow 
      // Tipo NIL / NULL ignora
      loop
    ElseIF !lOnlyStruct 
      // Nao é somente a estrutura ? Acrescenta no resultado 
      aadd(aShowResult,cRow)
    ElseIf "Tipo [J]" $ cRow .or. "Tipo [A]" $ cRow 
      // Somente estrutura , é um objeto ou array, mostra 
      aadd(aShowResult,cRow) 
    Endif
  Next
  conout('Resultado : '+cValtoChar(len(aShowResult)))
  oListResult:SetArray(aShowResult)

  // Joga o foco para a aba de resultados em caso de sucesso 
  oFolders:SHOWPAGE(3)

Else

  // Em caso de erro apenas limpa a aba de resultados
  oListResult:SetArray({' '})

Endif

Return

// ---------------------------------------------------------------------------- 
// Gera uma string com o conteúdo visivel de objetos na tela
// E copia para a área de transferencia

STATIC Function ToClipboard(oListResult)
Local aData := oListResult:aItems
LOcal cStr := '' ,nI 
For nI := 1 to len(aData)
  cStr += aData[nI] + chr(13)+chr(10)
Next
COPYTOCLIPBOARD(cStr)
MsgInfo('Conteúdo copiado para a área de transferencia')
Return

Conclusão

Esse post eu deixo as conclusões para vocês 😀 Espero que essa ferramenta lhes seja útil, agradeço novamente a audiência, e lhes desejo TERABYTES DE SUCESSO !!!

Referências

 

4 comentários sobre “JSON – O que é e como usar em AdvPL – Parte 03 – zJsonKit

  1. Bom dia Julio,

    Tudo bem?
    Show de bola… como sempre…
    Agora vou aproveitar a sua deixa, para tirar uma dúvida, meio relacionada ao assunto. Seguinte, fizemos um upload de arquivo, dentro de um rest utilizando justamente o objeto. Porém não conseguimos realizar o processo utilizando form-data, tivemos que passar a string com encode64, ou seja, quando passamos algo muito grande o server não recebeu o conteúdo da string, processou normalmente, mas não realizou a gravação gerando um arquivo com 0 Kb. aumentamos maxstringsize: 25 mega, o máximo que chegamos foi um arquivo de 6 mega. Isso estaria relacionado ao objeto, a limitação do server 32 ou ainda a utilização do rest? Conseguimos realizar o recebimento de arquivo com ADVPL utilizando rest utilizando form-data?

    Curtido por 1 pessoa

    • Rapaz .. bom dia 😀 Eu vou cofirmar, mas eu acho que upload de arquivo via HTTP usando multipart/form-data não é suportado pelo REST. O Protheus Server configurado como HTTP Server suporta upload, mas o REST é feito subindo uma porta dedicada com um interpretador HTTP Server em AdvPL. Quanto ao tamanho, em tese deveria suportar algo “gande” sim.. .se não tem nenhuma mensagem adicional de erro ou advertência no log de console após receber um post grande e não gravar nada, pode estar faltando algum tratamento, só vai ser possíve saber “por que” está falhando debugando o código do servidor REST. Se você reproduz a ocorrëncia, monta um exemplo funcional e abre um chamado, que a equipe do Framework investiga 😀

      Curtir

  2. Bom dia Julio.
    Muito esse e me fez olhar melhor para o Json x Advpl e com isso surgiu uma dúvida. Por gentileza se puder me informe se haveria melhora no processamento de relatórios utilizando um Json dentro do Protheus (sem uso de API) ?

    Obrigado.

    Curtido por 1 pessoa

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s