17

I was interested in finding (or if necessary developing) an XSLT equivalent for JSON.

As I have not found any, I was considering the possible query language to use for matching JSON paths so as to apply templates (from JavaScript) when there was a match (probably just checking an array of matching patterns in order, and stopping at the first template which matches, though allowing for the equivalent of xsl:apply-templates to keep templates going for children).

I am aware of JSONPath, JSONQuery, and RQL as JSON query languages (though I was not fully clear on whether RQL supported absolute and relative paths). Any suggestions on factors to consider and relative advantages of each toward such a usage.

Brett Zamir
  • 329
  • 1
  • 2
  • 10
  • Just a random thought, JavaScript and Mustache/Handlebars maybe? :) – Knerd Dec 05 '14 at 22:26
  • Thanks, but I'm more keen toward using a standardish approach (e.g., at least one with the potential, given that generic JSON path expressions would be a generically recognized mean of referencing JSON as opposed to some syntax specific to a library). – Brett Zamir Dec 05 '14 at 22:31
  • 5
    http://stackoverflow.com/questions/1618038/xslt-equivalent-for-json – Robert Harvey Dec 05 '14 at 22:44
  • 1
    I also found this interesting: http://json-template.googlecode.com/svn/trunk/doc/Introducing-JSON-Template.html – Robert Harvey Dec 05 '14 at 22:45
  • I have done Json -> XML -> XSLT -> Json before - it works fine, even if it's not the most efficient solution, – user2813274 Dec 05 '14 at 22:52
  • Yeah, thanks, I guess your solution @user2813274 (and specifically, Robert Harvey , the "Stapling" one mentioned at http://stackoverflow.com/a/10529400/271577 ) may meet my needs. If someone builds an XSLT 3.0 implementation in JS then we'd be set as it is to have functions for importing and converting JSON to XML (and back to JSON if desired). – Brett Zamir Dec 05 '14 at 23:00
  • There are XSLTs available for converting to/from json already like [this](http://www.bramstein.com/projects/xsltjson/) – user2813274 Dec 05 '14 at 23:36

5 Answers5

28

XML : XSLT :: JSON : x. What is x ?

The most facile answer would be x = JavaScript. Though you could make a case for this, it feels unsatisfying. Even though XSLT is technically Turing complete, there is a poor correspondence between the declarative style of XSLT and the more imperative or functional styles seen in JavaScript.

There are a few standalone JSON query languages, like JSONPath, JSONiq, and RQL which might stand in for the middle ground of XML : XPath :: JSON : y (or possibly, XQuery rather than XPath). And every JSON-focused document database has a JSON-related query language.

But the reality is, despite there being a few contenders for the full XSLT position, such as SpahQL, there are no generally accepted, broadly supported JSON equivalents to XSLT.

Why?

With all the JSON in the world, why isn't there a (more direct) analog to XSLT? Because many developers view XSLT as a failed experiment. Any search engine will lead to to quotes like "XSLT is a failure wrapped in pain." Others have argued that if it were just better formatted, it would be more popular. But interest in XSLT has generally diminished over the years. Many tools that support it support only version 1.0, which is a 1999 specification. Fifteen year old specs? There is a much newer 2.0 spec, and if people were enthusiastic about XSLT, it would be supported. It isn't.

By and large developers have chosen to process and transform XML documents with code, not transformation templates. It is therefore unsurprising that when working with JSON, they would also by and large choose to do that in their native language, rather than adding an additional "foreign" transformation system.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
Jonathan Eunice
  • 9,710
  • 1
  • 31
  • 42
  • 2
    +1 as this is a thoughtful reply, but I still think that it is cleaner to have a bunch of linearly arranged templates with a library doing the stepping through, and while I think you are probably right about the attitude toward XSL (I'd lean to the camp thinking it is a formatting issue though the recursive style admittedly needs some accustomization), I'd bet some of the problem may be inertia in needing to develop such a language in order to use it (e.g., I'm finding even JSONPath itself needs a few enhancements). – Brett Zamir Dec 11 '14 at 01:46
  • SpahQL didn't seem to have its own templates, so it still seems there are no contenders that actually use pure JavaScript or JSON for the template code (along with the data structures) even though there are libraries which allow expression of HTML as JSON/JS. – Brett Zamir Dec 11 '14 at 01:47
  • 1
    +1 despite the fact that there's something about XSLT that nothing else quite manages to replicate. JSON is certainly going to be a harder syntax to write a usable equivalent with. – user52889 Jan 21 '15 at 22:27
7

While Jonathan largely talks about the nature of XSLT as a language in his answer, I think there's another angle to consider.

The purpose of XSLT was to transform XML documents into some other document (XML, HTML, SGML, PDF, etc). In this way, XSLT is frequently used, effectively, as a template language.

There is a vast array of template libraries out there, even if you restrict yourself to JavaScript libraries (which you shouldn't need to, since the JS in JSON only refers to the genesis of the notation and should not be taken to imply that JSON is only for JavaScript). This template engine selector gives and indication of the variety of JS options there are out there.

The latter half of your questions talks more about query languages and the XML version of these would be XPath (not XSLT). As you noted, there are a variety of options there and I don't anything to add to that list. This area is relatively new, so I'd suggest you pick one and just go with it.

Dancrumb
  • 571
  • 5
  • 14
  • In case there's any doubt, I think Jonathan's answer is great; I just wanted to add an alternative perspective. – Dancrumb Dec 06 '14 at 21:04
  • Yes, fair points (and yes re: XPath being the equivalent for the second part), but I'm interested in seeing my JS XSL (calling it JTLT) utilize an enhanced JSONPath transform JSON into another language too (i.e., HTML as a string or DOM). – Brett Zamir Dec 11 '14 at 01:57
  • I have my own library called Jamilih which I favor for expressing raw HTML as JS/JSON, but I need something feeling natural and I hope catchy for 1) Templates and path matching 2) Iterating APIs equivalent to xsl:apply-templates and xsl:call-template (xsl:for-each is obvious for JS, but not JSON). For JS, I could use functions for templates, and for JSON (based on Jamilih and those iterating APIs). Wills ee how it goes... – Brett Zamir Dec 11 '14 at 01:59
4

I recently created a library, json-transforms, exactly for this purpose:

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

It uses a combination of JSPath, a DSL modelled on XPath, and a recursive pattern matching approach, inspired directly by XSLT.

Here's a quick example. Given the following JSON object:

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 }
  ]
};

Here's a transformation:

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);

Which output the following:

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

This transform is composed of three rules. The first matches any automobile which is made by Honda, emitting an object with a Honda property, then recursively matching. The second rule matches any object with a maker property, outputting the model and year properties. The final is the identity transform that recursively matches.

ColinE
  • 461
  • 3
  • 5
  • +1 and thanks for the info. I'm hoping to get my own https://github.com/brettz9/jtlt completed at some point, but it is helpful to have more implementations to compare. – Brett Zamir Jul 13 '16 at 15:27
3

Here are a few examples of what you can do with my (small[jslt.min.js]) JSLT -- JavaScript Lightweight Transforms:

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

([jslt.min.js] weighs ~ 3.1kb minified)

that is, just one function,

function Per ( subject ) { ... }

... which actually mimics XSLT (1.0)'s processing model.

(cf. the "transform" and "template" inner functions, in Per's body)

So, in essence, it's simply just all baked into that single function Per ( subject ) { ... } which forks its evaluation on the type of its (also) unique argument, to implement, either:

1) Array subject

nodeset creation / filtering / flattening / grouping / ordering / etc, if subject is an array, where the resulting nodeset (an Array as well) is extended with, and bound to methods named accordingly (only the returned Array instance of the call to Per ( subjectArray ) is extended; i.e., Array.prototype is left untouched)

i.e., Per :: Array --> Array

(the resulting Array's extension methods having self-explanatory names such as, groupBy, orderBy, flattenBy, etc -- cf. the usage in the examples)

2) String subject

string interpolation, if subject is a string

("Per" then returns an object with a method map ( source ), which is bound to the subject template string)

i.e., Per :: String --> { map :: (AnyValue --> String) }

e.g.,

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

yields:

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

while either of

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

or

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

yields the same:

"Those '0123456789' are our 10 digits."

but only

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

yields

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

3) Transform subject

XSLT look-alike transformation, if the subject is a hash with a conventionally-defined "$" member providing the array of rewriting rules (and same as in (2), "Per" then returns an object with a method map ( source ) bound to the subject transform -- where

"ruleName" in Per ( subjectTransform [ , ruleName ]) is optional and provides functionality similar to <xsl:call-template name="templateName">...)

i.e., Per :: (Transform [ , ruleName :: String ]) --> { map :: (AnyValue --> AnyValue) }

with

Transform :: { $ :: Array of rewriting rules[rw.r.] }

([rw.r.] predicate and template function pairs)

e.g., given (... another contrived example)

// (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);
      }
  ]
//...
] };

then

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

yields:

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

while... (much alike <xsl:call-template name="betterGenderString">...)

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

yields:

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

and

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

yields:

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

4) Otherwise

the identity function, in all other cases

i.e., Per :: T --> T

( i.e., Per === function ( value ) { return value ; } )

Note

in (3) above, a JavaScript's "this" in the body of a template function is thus bound to the container / owner Transform and its set of rules (as defined by the $: [ ... ] array) -- therefore, making the expression "Per(this)", in that context, a functionally-close equivalent to XSLT's

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

'HTH,

YSharp
  • 888
  • 6
  • 10
  • 1
    That's pretty cool. – Robert Harvey Mar 04 '16 at 02:38
  • @RobertHarvey : besides the terseness of the [section 5.1](https://www.w3.org/TR/xslt#section-Processing-Model) in and of itself that I had noticed long ago, I eventually also got intrigued and inspired by Evan Lenz's catchy remark "XSLT is simpler than you think!", at [http://www.lenzconsulting.com/how-xslt-works](http://www.lenzconsulting.com/how-xslt-works/) -- and so I decided to give a shot at verifying that claim (if only out of curiosity) in the very malleable language that is JavaScript. – YSharp Mar 04 '16 at 04:02
  • Thank you very much for your detailed reply. I'm busy with some other things (including my own XSLT equivalent), but I do intend to get back to this to take a more careful look. – Brett Zamir Mar 07 '16 at 21:36
-1

I don't think you will ever get an JSON variant for JSON per se. There exist several templating engines such as Python's Jinja2, JavaScripts Nunjucks, the Groovy MarkupTemplateEngine, and many others that should be well suited for what you want. .NET has T4 and JSON serialization/deserialization support so you have that also.

Since the derserialized JSON data would be basically a dictionary or map structure, that would just get past to your templating engine and you would iterate over the desired nodes there. The JSON data then gets transformed by the template.