miércoles, 17 de abril de 2019

Parser JSON para FOX (JSONFOX)

Hace unos meses comencé un proyecto llamado "El zorro peregrino" el cual consiste en mostrar el camino a través de la practica, a los foxeros que ya tienen sus años trabajando con este maravilloso lenguaje de programación; el camino mencionado es el de las nuevas tecnologías o tendencias de desarrollo.

He dicho por largo tiempo en grupos de debates y opiniones que la tecnología sube por ascensor y nosotros (los foxeros) subimos por escalera, esto quiere decir que cada dia literalmente nos hacemos más obsoletos y menos cotizables en el monstruoso mercado laboral.

Aún recuerdo aquella vez cuando fui a una entrevista de trabajo para desarrollador y me preguntaron que si hacía servicios web, entre mi dije, ¡Vaya, que será servicios web!, ¿será que son páginas web?, si digo que si entonces puede que me pregunten algo sobre eso y voy a quedar mal, más vale ser ignorante por 1 minuto que por toda la vida, mejor pregunto.

Les pregunté que si era hacer páginas web, me dijeron que sí entonces les dije que si sabia pero que no era mi fuerte. Más tarde me di cuenta de que me habian dicho que si solo porque ya no les interesaba y les daba igual mi respuesta.

Desde ese entonces sufri una gran decepción conmigo mismo porque no estaba al dia con los avances tecnológicos; me quedé estancado depurando errores en una empresa de software que terminaron cegándome al punto de sentir temor si algún dia me decidía a salirme del yugo patrono-empleado y comenzar mi carrera como freelancer.

Me di cuenta de que estaba muy por debajo del nivel que exigía el mundo tecnológico en ese momento asi que me dediqué a leer y actualizarme poco a poco, claro que siguiendo con Visual FoxPro porque era el que daba de comer (de hecho sigue dando).

Es por eso que comencé el proyecto El zorro peregrino, para que los programadores tengan una guia de como realizar la transición a este mundo moderno, seguramente muchos de los foxeros están desactualizados como me pasó a mi, lo puedo apostar porque en ese entonces era un venteañero, ahora tengo 33, soy un Foxero joven considerando los años que lleva el lenguaje y la cantidad de empresas de software que aún hoy dia siguen sostenidas por su software desarrollado en Visual FoxPro, lo cual se traduce en foxeros esparcidos por el mundo con un minimo de 15 años y un minimo de 45 años de edad.

Si fue dificil para mi adaptarme a las nuevas tendencias entonces no lo supone para ellos?

Bueno, dejemos tanta chachara y veamos el JSONFox

Es una libreria de código abierto que permite parsear JSON a un objeto o representación string, lo interesante de la librería es que nació de la necesidad de intercambiar información con las aplicaciones de Fox separadas en capas, por lo tanto era indispensable conseguir algún medio de transporte e intercambio de data entre los componentes de las capas y, sabiendo que Fox tiene compatibilidad bidireccional con Cursores y XML entonces se me ocurrió la idea de hacer el parser de JSON a XML y viceversa.

De esta manera ya tendríamos cubierta la necesidad de transportar data en formaro XML para luego serializar a un cursor (los cursores no se deben transferir entre capas)

Descarga la librería de github

https://github.com/Irwin1985/JSONFox

Hasta la próxima!!!



29 comentarios:

  1. Hola Irwin, he descargado tu clase JsonFox, voy a empezar a probarlo en un pequeño proyecto en VFP9.

    Por otro lado, qué pasó que ya no escribes más artículos? Son muy interesantes.

    Saludos desde Lima, Perú.

    ResponderEliminar
    Respuestas
    1. Hola Skysurfer, la verdad pensaba que nadie leía mis artículos. Voy a considerar escribir más entonces... :)

      Eliminar
  2. Buenas tardes Irwin, poco conozco del desarrollo web, para que me sirve el formato Json desde Foxpro, me puede servir para subir datos desde una base de datos de SQL Server a MySQL a un hosting web? Gracias de antemano

    ResponderEliminar
    Respuestas
    1. Sí, JSON es el formato estándar para el intercambio de datos entre aplicaciones o servicios web y otras aplicaciones de cualquier tipo (incluyendo desktop) por lo tanto para poder hablar con cualquier API por ahí de afuera es necesario conocer su lenguaje común que es HTTPRequest con respuestas JSON. Eso incluye desde luego comunicaciones con servidores de bases de datos siempre y cuando exista una API de por medio que te reciba las peticiones, te ejecute las consultas y te devuelva las respuestas.

      Eliminar
  3. Buen dia. Ante todo, agradezco tu importante aporte al abrir la librería para su consumo a toda la comunidad. Haciendo pruebas, noto que si deseo parsear un objeto que tiene una propiedad "Collection" con items incluidos, la coleccion no es parseada. Te paso un breve ejemplo:
    do c:\jsonfox-master\jsonfox.app
    ox = Createobject("Empty")

    Addproperty(ox,"id",19191)
    Addproperty(ox,"nombre","Fulano")
    Addproperty(ox,"apellido","De Tal")

    oY = Createobject("Collection")
    oZ = Createobject("Empty")
    Addproperty(oZ,"Nombre","Juana")
    Addproperty(oZ,"Dni",36890765)
    oY.Add(oz)
    oZ = Createobject("Empty")
    Addproperty(oZ,"Nombre","Laura")
    Addproperty(oZ,"Dni",33786545)
    oY.Add(oz)

    Addproperty(ox,"hijas",oY)

    cx = _screen.json.stringify(ox)

    Hay algo que yo esté haciendo mal?
    Nuevamente, muchas gracias

    ResponderEliminar
    Respuestas
    1. Ya he realizado la mejora y la puedes descargar desde aquí: https://github.com/Irwin1985/JSONFox

      Fue divertido realizar el cambio gracias a la estructura del código de la librería, hice todas las pruebas que se me ocurrieron pero ya sabes que siempre se escapa algo así que bienvenida sea cualquier otra detección de bugs.

      Saludos!

      Eliminar
  4. Hola Alberto muchas gracias por usar la herramienta y en especial por darte cuenta de este bug de la librería. He tomado tu ejemplo como base para debuggear y me di cuenta que los objetos "Collection" pasan desapercibidos ante la función AMEMBERS() lo cual en efecto genera el vacío en todos los objetos que hereden de Collection. No me pasó por la mente testearlos pues siempre estuve cegado con la maravillosa clase "Empty".

    Me has dado trabajo así que tendré la librería bajo mantenimiento hasta liberar la revisión. Debes marcar el repositorio como "Watching" para que te enteres cuando la libere y puedas probarla.

    Saludos!

    ResponderEliminar
  5. Saludos Irvin, felicitaciones por tus aportes, estoy probando _Screen.Json.JSONToCursor(lcJsonArray, "qTemp"), crea el cursor los campos pero sin datos y me muestra el mensaje "Data type mismatch", al probarlo desde un *.prg funciona normal, pero al ejecutarlo desde el formulario de mi aplicación me muestra ese mensaje "Data type mismatch", gracias por tu tiempo.

    ResponderEliminar
  6. Hola Fernando, ayúdame con tu contenido del array para poder hacer las pruebas con tu misma data y recrear el error. Gracias!

    ResponderEliminar
  7. Hola Irwin muchas gracias por el aporte, disculpa se tiene alguna dificultad con la librería si la fecha comienza con el día en lugar de con el año? ya que me sale un error relacionado a la fecha. Gracias.

    ResponderEliminar
  8. Hola, sí la tiene porque normalmente los webservices te devuelven la fecha en formato YYYY-mm-dd. Qué formato le estás pasando?

    ResponderEliminar
  9. Muchas gracias por la respuesta, es un servicio web del diario oficial de la federación. y la fecha me la envía: dd-mm-YYYY. este es el servicio: https://sidofqa.segob.gob.mx/dof/sidof/indicadores/

    Sabes de alguna forma en la que pueda convertir la respuesta Json de ese servicio en XML para poder pintarla en el cursor. ? Gracias de ante mano

    ResponderEliminar
  10. Ya está agregada la mejora, ahora JSONFox puede soportar fechas con formado dd-mm-YYYY y dd/mm/YYYY. https://github.com/Irwin1985/JSONFox

    Y para que lleves el Array a Cursor tienes este ejemplo:

    Text To lcJsonStr NoShow
    {
    "messageCode": 200,
    "response": "OK",
    "ListaIndicadores": [
    {
    "codIndicador": 31001,
    "codTipoIndicador": 158,
    "fecha": "23-02-2021",
    "valor": "20.6783"
    },
    {
    "codIndicador": 30963,
    "codTipoIndicador": 159,
    "fecha": "23-02-2021",
    "valor": "6.694871"
    },
    {
    "codIndicador": 31002,
    "codTipoIndicador": 165,
    "fecha": "23-02-2021",
    "valor": "4.2875%"
    },
    {
    "codIndicador": 31003,
    "codTipoIndicador": 166,
    "fecha": "23-02-2021",
    "valor": "4.2620%"
    },
    {
    "codIndicador": 31004,
    "codTipoIndicador": 175,
    "fecha": "23-02-2021",
    "valor": "4.04%"
    }
    ]
    }
    endtext
    obj = _screen.json.parse(lcJsonStr)
    ACOPY(obj.listaindicadores, otroArray)
    lcArray = _screen.json.stringify(@otroArray)
    ?lcArray
    _screen.json.jsontocursor(lcArray, "qData")
    select qData
    browse

    ResponderEliminar
  11. Muchas gracias por el apoyo, estoy comenzando en el mundo del VFP por un proyecto de mi trabajo y solo se usar tu librería la que termina en prg y no la que termina en app, con esta actualización que hiciste también se vería reflejado en la prg?

    ResponderEliminar
  12. loRest = NEWOBJECT("rest", "C:\Users\capacitaciones\Documents\VFPRestClient-master\vfprestclient.prg")
    loRest.AddRequest(loRest.GET, "https://sidofqa.segob.gob.mx/dof/sidof/indicadores/")
    loRest.Send()
    ?loRest.status
    ?loRest.lASTERRORTEXT
    ?loRest.resPONSE
    loJson = NEWOBJECT ("jsonfox","C:\Users\capacitaciones\Documents\JSONFox\jsonfox.prg")
    obj = loJson.decode(loRest.resPONSE)
    ?obj
    cXML = loJson.arraytoxml(obj._listaindicadores)
    ?cxml
    XMLTOCURSOR(cxml,"prueba")
    browse

    este es el codigo que estoy utilizando

    ResponderEliminar
  13. Usalo así:
    loRest = NEWOBJECT("rest", "C:\Users\capacitaciones\Documents\VFPRestClient-master\vfprestclient.prg")
    loRest.AddRequest(loRest.GET, "https://sidofqa.segob.gob.mx/dof/sidof/indicadores/")
    loRest.Send()
    Do locfile("jsonfox", "app")
    obj = _screen.json.parse(loRest.resPONSE)
    ACOPY(obj.listaindicadores, otroArray)
    lcArray = _screen.json.stringify(@otroArray)
    _screen.json.jsontocursor(lcArray, "qData")
    select qData
    browse

    ResponderEliminar
  14. Muchas gracias amigo por el apoyo, ya quedó. al momento de ejecutarlo me sale el visual FoxPro Debugger [break]-jasonclass.prg:jsonclass.parse. precioso play y ya se genera el cursor, hay alguna forma de que se pueda ejecutar el cursor si tener que presionar el play del debugger? Gracias y si me pudieras propocionar alguna numero de cuenta de bbva para hacerte una transfer. Saludos.

    ResponderEliminar
  15. Revisa si la JsonClass.prg tiene una instrucción SET STEP ON y quítala.

    ResponderEliminar
  16. Irwin,me quito el sombrero por la facilidad con que has realizado y mostrado la integración del fox con las nuevas tecnologías.
    Muchas gracias por tus aportes

    ResponderEliminar
  17. Hola Jorge gracias a ti por estar atento a mis publicaciones. Un saludo!

    ResponderEliminar
  18. Buenas tardes Irwin, estoy probando tu librería, no se si estoy haciendo algo mal, pero solo convierte a cursor (JsonToCursor) el primer registro, aunque hay 2 en el Json, espero puedan ayudarme a ver que me falta, gracias. Aquí el código:
    ** PRUEBA JSONFOX
    SET DECIMALS TO 4
    Do LocFile("JSONFox", "app")

    SET MEMOWIDTH TO 8000
    LC_JSTEXT = ""
    TEXT TO LC_JSTEXT NOSHOW ADDITIVE
    {"status": "success",
    "vart_ajus":[
    {"art_check":1,"pk_lam":"00001","fact_num":0,"reng_num":1,"art_num":1,"co_art":"LGIS001","co_alma":"000012","art_alto":1.2568,"art_ancho":2.4550,"art_dim":3.0854,"art_alto_stock":0,"art_ancho_stock":0,"art_dim_stock":0,"art_unid":"","art_ubic":"","art_pint":"","art_iso":"000000000000","art_com":"","art_fec":"2021-06-27 12:00 AM"},
    {"art_check":1,"pk_lam":"00002","fact_num":0,"reng_num":1,"art_num":2,"co_art":"LGIS001","co_alma":"000012","art_alto":2,"art_ancho":1,"art_dim":2,"art_alto_stock":0,"art_ancho_stock":0,"art_dim_stock":0,"art_unid":"","art_ubic":"","art_pint":"","art_iso":"000000000000","art_com":"","art_fec":"2021-06-27 12:00 AM"}
    ],
    "code": 200,
    "message": "success"}
    ENDTEXT
    *LC_JSTEXT = Strtran(LC_JSTEXT, Chr(13)+Chr(10), "") && Eliminar los saltos de línea

    obj = _Screen.Json.Parse(LC_JSTEXT)
    If _Screen.Json.lError
    MessageBox(_Screen.Json.LastErrorText, 48, "Something went wrong")
    Return
    ENDIF

    * Encode just the Array attribute called (data)*
    lcJsonArray = _Screen.Json.Encode(obj.vart_ajus)

    * Surround the lcJSONArray with array character delimiters '[' ']'.
    lcJsonArray = "[" + lcJsonArray + "]"
    *messagebox(lcJsonArray)
    * Convert the JSONArray into VFP CURSOR **(this is cool)**
    _Screen.Json.JsonToCursor(lcJsonArray, "cursorxx")

    BROWSE NORMAL

    ResponderEliminar
  19. Hola José,

    Te devuelvo el mismo ejemplo con los cambios enumerados para que sepas lo que hubo que hacer para tratar los arrays.

    ** PRUEBA JSONFOX
    SET DECIMALS TO 4
    Do LocFile("JSONFox", "app")

    SET MEMOWIDTH TO 8000
    LC_JSTEXT = ""
    TEXT TO LC_JSTEXT NOSHOW ADDITIVE
    {"status": "success",
    "vart_ajus":[
    {"art_check":1,"pk_lam":"00001","fact_num":0,"reng_num":1,"art_num":1,"co_art":"LGIS001","co_alma":"000012","art_alto":1.2568,"art_ancho":2.4550,"art_dim":3.0854,"art_alto_stock":0,"art_ancho_stock":0,"art_dim_stock":0,"art_unid":"","art_ubic":"","art_pint":"","art_iso":"000000000000","art_com":"","art_fec":"2021-06-27 12:00 AM"},
    {"art_check":1,"pk_lam":"00002","fact_num":0,"reng_num":1,"art_num":2,"co_art":"LGIS001","co_alma":"000012","art_alto":2,"art_ancho":1,"art_dim":2,"art_alto_stock":0,"art_ancho_stock":0,"art_dim_stock":0,"art_unid":"","art_ubic":"","art_pint":"","art_iso":"000000000000","art_com":"","art_fec":"2021-06-27 12:00 AM"}
    ],
    "code": 200,
    "message": "success"}
    ENDTEXT
    *LC_JSTEXT = Strtran(LC_JSTEXT, Chr(13)+Chr(10), "") && Eliminar los saltos de línea

    obj = _Screen.Json.Parse(LC_JSTEXT)
    If _Screen.Json.lError
    MessageBox(_Screen.Json.LastErrorText, 48, "Something went wrong")
    Return
    ENDIF

    * Encode just the Array attribute called (data)*
    *===========================================================*
    * 1. Copiar el array desde obj.vart_ajus a array1
    *===========================================================*
    acopy(obj.vart_ajus, array1)
    *===========================================================*
    * 2. pasar la copia array1 como referencia usando '@'
    *===========================================================*
    lcJsonArray = _Screen.Json.Encode(@array1)

    * Surround the lcJSONArray with array character delimiters '[' ']'.
    *===========================================================================*
    * 3. No hace falta delimitar con corchetes porque ya se trata de un array.
    *===========================================================================*
    *lcJsonArray = "[" + lcJsonArray + "]"
    *messagebox(lcJsonArray)
    * Convert the JSONArray into VFP CURSOR **(this is cool)**
    _Screen.Json.JsonToCursor(lcJsonArray, "cursorxx")

    BROWSE NORMAL

    ResponderEliminar
  20. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  21. Estimados

    Estoy recien incursionando en las api de las cuales me devuelve un JSON
    Necesito de su ayuda como puedo pasar a una tabla o cursor los datos que me entrega una api las cuales son tandas de 50 filas de la siguiente forma

    Para obtener los datos uso lo siguiente

    Public lcjonStr as string
    xmlHttp = CREATEOBJECT("Microsoft.XMLHTTP")
    xmlHttp.open("get", 'https://api.xxx/api/v2/unidad/585/detalles', .f.)
    xmlHttp.setRequestHeader("Authorization","Token yyyy")
    xmlHttp.setRequestHeader("Content-Type", "application/json")
    xmlHttp.send()
    IF xmlHttp.status = 200
    lcJsonStr=STRCONV(xmlHttp.responseText,1)

    y me devuelve
    {
    "links": { "current": "https://api.xxx/resumen?cursor=NUl899o80="
    },
    "data": [
    {
    "id": 882
    "id_chek": 576,
    "total": 113740,
    "organizacion": {
    "4.3": [
    "Octogonal"
    ]
    },
    "diagnosticos": [],
    "links": [
    {
    "rel": "self",
    "href": "https://api.xxx/resumen/882",
    "method": "GET"
    }
    ]
    },
    {
    "id": 882,
    "id_check": 576,
    "total": 2310,
    "organizacion": {
    "2.6": [
    "Cuadrada"
    ]
    },
    "diagnosticos": [],
    "links": [
    {
    "rel": "self",
    "href": "https://api.xxx/resumen/882",
    "method": "GET"
    }
    ]
    }

    ]
    }

    En este ejercicio recibo dos filas. NO se como recorrerlo o pasar a cursor ya que me dice data no encontrada. Muchas Gracias de antemano

    ResponderEliminar
    Respuestas
    1. Esto es lo que sucede, tu array no es compatible con el esquema de una tabla o cursor (columna, fila) y por eso no es posible hacer la conversión a cursor directamente.

      Tu array contiene un objeto llamado "organización" y ese es el que está impidiendo la conversión. La solución en estos casos es recorrer el array de forma manual e ir sacando los datos en un cursor maestro y otro detalle porque imagino que quieres conservar la relación de la data con los diagnósticos.

      Puedo ayudarte de forma gratuita pero no te prometo fecha de entrega porque ahora estoy siendo más padre que programador :). Por otra parte, ofrezco consultorías los Martes y Jueves de 8pm a 10pm (hora España) por si lo necesitas urgente. Aquí te dejo mi correo por si me quieres escribir directamente: rodriguez.irwin@gmail.com

      Eliminar
  22. MUchas gracias, como se recorro manualmente el array como saber la cantidad de filas antes usaba el json.prg pero ahora no me reconoce
    n = ALEN(objeto._data.array)
    para recorrerlo como
    for i = 1 to n
    ? objeto._data.array[i]._diagnostico
    next

    Gracias por tu pronta respuesta

    ResponderEliminar
  23. Estimado era simple

    n = ALEN(obj.data)

    ResponderEliminar
  24. Hola

    esto me funciona, pero como sería con tu función, ya que no encuentro la forma de pasarle la clave

    loXml = CREATEOBJECT("MSXML2.XMLHTTP")
    loxml.Open("GET", "url", .t.)
    loxml.setRequestHeader("Content-Type", "application/json")
    loxml.setRequestHeader("Authorization", "Basic clave")

    ResponderEliminar