XSLT equivalente para JSON

14

Estaba interesado en encontrar (o, si fuera necesario, desarrollar) un equivalente XSLT para JSON.

Como no he encontrado ninguno, estaba considerando el posible lenguaje de consulta que se usaría para hacer coincidir las rutas JSON para aplicar plantillas (desde JavaScript) cuando había una coincidencia (probablemente solo verificaba una matriz de patrones coincidentes en orden y me detenía en el primera plantilla que coincide, aunque permite el equivalente de xsl: apply-templates para mantener las plantillas para niños).

Soy consciente de JSONPath, JSONQuery y RQL como lenguajes de consulta JSON (aunque no estaba completamente claro si RQL admitía rutas absolutas y relativas). Cualquier sugerencia sobre los factores a considerar y las ventajas relativas de cada uno hacia tal uso.

Brett Zamir
fuente
Solo un pensamiento al azar, ¿tal vez JavaScript y Bigote / Manillar? :)
Knerd
Gracias, pero estoy más interesado en usar un enfoque estándar (por ejemplo, al menos uno con potencial, dado que las expresiones de ruta JSON genéricas serían un medio genéricamente reconocido de hacer referencia a JSON en lugar de alguna sintaxis específica de una biblioteca).
Brett Zamir
1
También me pareció interesante: json-template.googlecode.com/svn/trunk/doc/…
Robert Harvey
He hecho Json -> XML -> XSLT -> Json antes - funciona bien, incluso si no es la solución más eficiente,
user2813274

Respuestas:

27

XML: XSLT :: JSON: x . ¿Qué es x ?

La respuesta más fácil sería x = JavaScript. Aunque podría presentar un caso para esto, se siente insatisfactorio. A pesar de que XSLT está técnicamente completo , existe una mala correspondencia entre el estilo declarativo de XSLT y los estilos más imperativos o funcionales que se ven en JavaScript.

Existen algunos lenguajes de consulta JSON independientes, como JSONPath , JSONiq y RQL, que pueden representar el término medio de XML: XPath :: JSON: y (o posiblemente, XQuery en lugar de XPath). Y cada base de datos de documentos centrada en JSON tiene un lenguaje de consulta relacionado con JSON .

Pero la realidad es que, a pesar de que hay algunos contendientes para la posición XSLT completa, como SpahQL , no hay equivalentes JSON generalmente aceptados y ampliamente compatibles con XSLT.

¿Por qué?

Con todos los JSON del mundo, ¿por qué no hay un análogo (más directo) de XSLT? Porque muchos desarrolladores ven XSLT como un experimento fallido. Cualquier motor de búsqueda llevará a citas como "XSLT es un fracaso envuelto en dolor". Otros han argumentado que si estuviera mejor formateado, sería más popular. Pero el interés en XSLT generalmente ha disminuido con los años . Muchas herramientas que lo admiten solo admiten la versión 1.0 , que es una especificación de 1999. ¿Especificaciones de quince años? Hay una especificación 2.0 mucho más nueva, y si la gente estuviera entusiasmada con XSLT, sería compatible. No lo es

En general, los desarrolladores han optado por procesar y transformar documentos XML con código, no con plantillas de transformación. Por lo tanto, no es sorprendente que al trabajar con JSON, en general, elijan hacerlo en su idioma nativo, en lugar de agregar un sistema de transformación "extranjero" adicional.

Jonathan Eunice
fuente
2
+1 ya que esta es una respuesta reflexiva, pero sigo pensando que es más limpio tener un montón de plantillas organizadas linealmente con una biblioteca haciendo el paso, y aunque creo que probablemente tenga razón sobre la actitud hacia XSL (yo apóyate en el campamento pensando que es un problema de formateo, aunque el estilo recursivo ciertamente necesita cierta personalización), apuesto a que parte del problema puede ser la inercia en la necesidad de desarrollar un lenguaje para usarlo (por ejemplo, estoy descubriendo incluso JSONPath sí mismo necesita algunas mejoras).
Brett Zamir
SpahQL no parecía tener sus propias plantillas, por lo que todavía parece que no hay contendientes que realmente utilicen JavaScript puro o JSON para el código de plantilla (junto con las estructuras de datos) a pesar de que hay bibliotecas que permiten la expresión de HTML como JSON / JS.
Brett Zamir
1
+1 a pesar del hecho de que hay algo en XSLT que nada más logra replicar. Ciertamente, JSON será una sintaxis más difícil para escribir un equivalente utilizable.
user52889
7

Si bien Jonathan habla en gran medida sobre la naturaleza de XSLT como lenguaje en su respuesta, creo que hay otro ángulo a considerar.

El propósito de XSLT era transformar documentos XML en algún otro documento (XML, HTML, SGML, PDF, etc.). De esta manera, XSLT se usa con frecuencia, efectivamente, como un lenguaje de plantilla.

Existe una gran variedad de bibliotecas de plantillas, incluso si se restringe a las bibliotecas de JavaScript (lo cual no debería necesitar, ya que el JS en JSON solo se refiere a la génesis de la notación y no debe interpretarse que implica que JSON es solo para JavaScript). Este selector de motor de plantilla ofrece una indicación de la variedad de opciones de JS que existen.

La última mitad de sus preguntas habla más sobre los lenguajes de consulta y la versión XML de estos sería XPath (no XSLT). Como notó, hay una variedad de opciones allí y no tengo nada que agregar a esa lista. Esta área es relativamente nueva, por lo que te sugiero que elijas una y la sigas.

Dancrumb
fuente
En caso de que haya alguna duda, creo que la respuesta de Jonathan es excelente; Solo quería agregar una perspectiva alternativa.
Dancrumb
Sí, puntos justos (y sí, re: XPath es el equivalente para la segunda parte), pero estoy interesado en ver que mi JS XSL (llamándolo JTLT) utilice una JSONPath mejorada para transformar JSON en otro idioma también (es decir, HTML como un cadena o DOM).
Brett Zamir
Tengo mi propia biblioteca llamada Jamilih, que prefiero para expresar HTML sin formato como JS / JSON, pero necesito algo natural y espero pegadizo para 1) Plantillas y coincidencia de rutas 2) API iterativas equivalentes a xsl: apply-templates y xsl: call-template (xsl: for-each es obvio para JS, pero no para JSON). Para JS, podría usar funciones para plantillas y para JSON (basado en Jamilih y esas API iterativas). Quiere ver cómo va ...
Brett Zamir
3

Aquí hay algunos ejemplos de lo que puede hacer con mi (pequeño [jslt.min.js] ) JSLT - Transformaciones ligeras de JavaScript:

https://jsfiddle.net/YSharpLanguage/c7usrpsL/10

( [jslt.min.js] pesa ~ 3.1kb minificado )

es decir, solo una función,

function Per ( subject ) { ... }

... que realmente imita el modelo de procesamiento de XSLT (1.0) .

(cf. las funciones internas "transformar" y "plantilla", en el cuerpo de Per)

Entonces, en esencia, simplemente se integra todo en ese único function Per ( subject ) { ... }que bifurca su evaluación sobre el tipo de su (también) argumento único, para implementar, ya sea:

1) Asunto de la matriz

creación / filtrado / aplanamiento / agrupación / ordenación / conjunto de nodos, si el sujeto es una matriz, donde el conjunto de nodos resultante (también una matriz ) se amplía y se vincula a los métodos nombrados en consecuencia ( solo la instancia de matriz devuelta de la llamada Per ( subjectArray )es extendido; es decir, Array.prototype se deja intacto)

es decir, Per :: Array --> Array

(los métodos de extensión de la matriz resultante tienen nombres que se explican por sí mismos, como groupBy, orderBy, flattenBy, etc., véase el uso en los ejemplos)

2) Asunto de cuerda

interpolación de cadenas , si el sujeto es una cadena

("Por" luego devuelve un objeto con un método map ( source ), que está vinculado a la cadena de plantilla del asunto )

es decir, Per :: String --> {map :: ( AnyValue --> String )}

p.ej,

Per("Hi honey, my name is {last}. {first}, {last}.").map({ "first": "James", "last": "Bond" })

rendimientos:

"Hi honey, my name is Bond. James, Bond."

mientras cualquiera de

Per("Those '{*}' are our 10 digits.").map([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ])

o

Per("Those '{*}' are our 10 digits.").map(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

produce lo mismo:

"Those '0123456789' are our 10 digits."

pero sólo

Per("Those '{*}' are our 10 digits.").map([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], ", ")

rendimientos

"Those '0, 1, 2, 3, 4, 5, 6, 7, 8, 9' are our 10 digits."

3) Transformar sujeto

Transformación similar a XSLT , si el sujeto es un hash con un miembro "$" convencionalmente definido que proporciona la matriz de reglas de reescritura (e igual que en (2), "Per" luego devuelve un objeto con un método map ( source )vinculado al sujeto transformar - donde

"ruleName" en Per ( subjectTransform [ , ruleName ])es opcional y proporciona una funcionalidad similar a <xsl: nombre de la llamada-template = "nombre_plantilla"> ...)

es decir, Per :: ( Transform [, ruleName :: String ]) -->{map :: ( AnyValue --> AnyValue )}

con

Transformar :: {$ :: Matriz de reescritura de reglas [rw.r.] }

( pares de funciones de predicado y plantilla [rw.r.] )

por ejemplo, dado (... otro ejemplo artificial)

// (A "Member" must have first and last names, and a gender)
function Member(obj) {
  return obj.first && obj.last && obj.sex;
}

var a_transform = { $: [
//...
  [ [ Member ], // (alike <xsl:template match="...">...)
      function(member) {
        return {
          li: Per("{first} {last}").map(member) +
              " " +
              Per(this).map({ gender: member.sex })
        };
      }
  ],

  [ [ function(info) { return info.gender; } ], // (alike <xsl:template match="...">...)
      function(info) { return Per("(gender: {gender})").map(info); }
  ],

  [ [ "betterGenderString" ], // (alike <xsl:template name="betterGenderString">...)
      function(info) {
        info.pronoun = info.pronoun || "his/her";
        return Per("({pronoun} gender is {gender})").map(info);
      }
  ]
//...
] };

luego

Per(a_transform).map({ "first": "John", "last": "Smith", "sex": "Male" })

rendimientos:

{ "li": "John Smith (gender: Male)" }

mientras ... (muy parecido <xsl:call-template name="betterGenderString">...)

"James Bond... " +
Per(a_transform, "betterGenderString").map({ "pronoun": "his", "gender": "Male" })

rendimientos:

"James Bond... (his gender is Male)"

y

"Someone... " +
Per(a_transform, "betterGenderString").map({ "gender": "Male or Female" })

rendimientos:

"Someone... (his/her gender is Male or Female)"

4) De lo contrario

la función de identidad , en todos los demás casos

es decir, Per :: T --> T

(es decir, Per === function ( value ) { return value ; })

Nota

en (3) arriba, un "this" de JavaScript en el cuerpo de una función de plantilla está vinculado al contenedor / propietario Transform y su conjunto de reglas (como se define en la matriz $: [...]) - por lo tanto, haciendo la expresión "Per (this)", en ese contexto, un equivalente funcionalmente cercano a XSLT

<xsl:apply-templates select="..."/>

«HTH

YSharp
fuente
1
Eso es muy bonito.
Robert Harvey
@RobertHarvey: además de la brevedad de la sección 5.1 en sí misma que había notado hace mucho tiempo, eventualmente también me intrigó e inspiró la observación pegadiza de Evan Lenz "¡XSLT es más simple de lo que piensas!", En http: // www. lenzconsulting.com/how-xslt-works , por lo que decidí intentar verificar esa afirmación (aunque solo fuera por curiosidad) en el lenguaje muy maleable que es JavaScript.
YSharp
Muchas gracias por tu respuesta detallada. Estoy ocupado con algunas otras cosas (incluido mi propio equivalente XSLT), pero tengo la intención de volver a esto para echar un vistazo más cuidadoso.
Brett Zamir
3

Recientemente creé una biblioteca, json-transforma , exactamente para este propósito:

https://github.com/ColinEberhardt/json-transforms

Utiliza una combinación de JSPath , un DSL modelado en XPath y un enfoque de coincidencia de patrones recursivo, inspirado directamente por XSLT.

Aquí hay un ejemplo rápido. Dado el siguiente objeto JSON:

const json = {
  "automobiles": [
    { "maker": "Nissan", "model": "Teana", "year": 2011 },
    { "maker": "Honda", "model": "Jazz", "year": 2010 },
    { "maker": "Honda", "model": "Civic", "year": 2007 },
    { "maker": "Toyota", "model": "Yaris", "year": 2008 },
    { "maker": "Honda", "model": "Accord", "year": 2011 }
  ]
};

Aquí hay una transformación:

const jsont = require('json-transforms');
const rules = [
  jsont.pathRule(
    '.automobiles{.maker === "Honda"}', d => ({
      Honda: d.runner()
    })
  ),
  jsont.pathRule(
    '.{.maker}', d => ({
      model: d.match.model,
      year: d.match.year
    })
  ),
  jsont.identity
];

const transformed  = jsont.transform(json, rules);

Lo que produce lo siguiente:

{
  "Honda": [
    { "model": "Jazz", "year": 2010 },
    { "model": "Civic", "year": 2007 },
    { "model": "Accord", "year": 2011 }
  ]
}

Esta transformación se compone de tres reglas. El primero coincide con cualquier automóvil fabricado por Honda, que emite un objeto con una Hondapropiedad y luego coincide de forma recursiva. La segunda regla coincide con cualquier objeto con una makerpropiedad, generando las propiedades modely year. La final es la transformación de identidad que coincide recursivamente.

ColinE
fuente
+1 y gracias por la información. Espero completar mi propio github.com/brettz9/jtlt en algún momento, pero es útil tener más implementaciones para comparar.
Brett Zamir
-1

No creo que alguna vez obtenga una variante JSON para JSON per se. Existen varios motores de plantillas, como Jinja2 de Python, Nunjucks de JavaScripts, Groovy MarkupTemplateEngine y muchos otros que deberían ser adecuados para lo que desea. .NET tiene soporte de serialización / deserialización T4 y JSON, por lo que también tiene eso.

Dado que los datos JSON desrserializados serían básicamente una estructura de diccionario o mapa, eso simplemente pasaría a su motor de plantillas y usted iteraría sobre los nodos deseados allí. La plantilla transforma los datos JSON.

greenaj
fuente