'JSONPath to transform and map to different tiers of data with a single output

I am using JSONPath and this node module https://www.npmjs.com/package/jsonpath-object-transform to transform a JSON obj into a different output. This is similar to XML and XSLT. The problem is when I want to map to different tiers of the source data and send to a flat output.

Examples:

var output = {
    storeName: '$.store',
    items: ['$.items',{
        uniqueName: '$.name',
        sku: '$.skus[*].num'
    }]
};

var source = {
    store:'my store',
    items: [{
        name: 'Hammer',
        skus: [{
            num: '12345qwert'
        }]
    }, {
        name: 'Bike',
        skus: [{
            num: 'asdfghhj'
        }, {
            num: 'zxcvbn'
        }]
    }, {
        name: 'Fork',
        skus: [{
            num: '0987dfgh'
        }]
    }]
};

The result of this is:

{
  "storeName": "my store",
  "items": [
    {
      "uniqueName": "Hammer",
      "sku": "12345qwert"
    },
    {
      "uniqueName": "Bike",
      "sku": "asdfghhj"
    },
    {
      "uniqueName": "Fork",
      "sku": "0987dfgh"
    }
  ]
}

The problem with the result is that it ignored the second array item in the Bike obj. items[1].skus[1].num. I believe it did this because $.items designator only knows of 3 obj in the items array.

My expected result was :

{
      "storeName": "my store",
      "items": [
        {
          "uniqueName": "Hammer",
          "sku": "12345qwert"
        },
        {
          "uniqueName": "Bike",
          "sku": "asdfghhj"
        },
{
          "uniqueName": "Bike",
          "sku": "zxcvbn"
        },
        {
          "uniqueName": "Fork",
          "sku": "0987dfgh"
        }
      ]
    }

How can I achieve this output using JSONPath.



Solution 1:[1]

I tackled this kind of problem with my own small library which essentially mimics the XSLT processing model; so, here's a fiddle for your example:

https://jsfiddle.net/YSharpLanguage/2sa0twny

where:

var source =
{
    store:'my store',
    items: [{
        name: 'Hammer',
        skus: [{
            num: '12345qwert'
        }]
    }, {
        name: 'Bike',
        skus: [{
            num: 'asdfghhj'
        }, {
            num: 'zxcvbn'
        }]
    }, {
        name: 'Fork',
        skus: [{
            num: '0987dfgh'
        }]
    }]
};

function Root(node) {
  return (typeof node.store === "string") &&
         ({ }.toString.call(node.items) === "[object Array]");
}

function SKU(node) {
  return typeof node.num === "string";
}

function FlattenedSKU(node) {
  return SKU(node) && (typeof node.name === "string");
}

var SO_30221930_Transform = { $: [

  [ [ Root ],
    // rewriting rule to process JSONPath's "$"
    // (XSLT-would-be <xsl:template match="/">...)
    function(root) {
      return {

        // (cf. the `String.prototype.of` interpolation helper)
        storeName: "{store}".of(root),

        // (XSLT-would-be <xsl:apply-templates select="items/sku"/>)
        items: root.items.
               // first, get all the SKU nodes (XPath "items//sku")
               nodeset(SKU, true).
               // then, flatten them as [ { name: ..., num: ... } ... ]
               map(function(sku_node) {
                 return {
                   name: "{name}".of(sku_node.parent.parent),
                   num: "{num}".of(sku_node.value),
                 };
               }).
               // finally, rewrite them as [ { uniqueName: ..., num: ... } ... ]
               through(this)
      }
    }
  ],

  [ [ FlattenedSKU ],
    function(sku) {
      return {
        uniqueName: "{name}".of(sku),
        num: "{num}".of(sku)
      };
    }
  ]

] };

var output = source.through(SO_30221930_Transform);
console.log(JSON.stringify(output, null, 2));

gives:

{
  "storeName": "my store",
  "items": [
    {
      "uniqueName": "Hammer",
      "num": "12345qwert"
    },
    {
      "uniqueName": "Bike",
      "num": "asdfghhj"
    },
    {
      "uniqueName": "Bike",
      "num": "zxcvbn"
    },
    {
      "uniqueName": "Fork",
      "num": "0987dfgh"
    }
  ]
}

Here are other examples / use cases:

'Hope this helps,

Solution 2:[2]

This may not be possible. You are really iterating over skus, not items, so in theory the expression should be something like this:

var output = {
  storeName: '$.store',
  items: ['$.items[*].skus[*]', { // or '$..skus[*]', or perhaps even '$..num'
    uniqueName: '$<<name', // no "parent" operator in JSONPath
    sku: '$.num'
  }]
}

But there are two problems:

1) jsonpath-object-transform appears not to properly transform children: leaving out uniquename, if you omit the transform, the correct num entries are produced, but if you add a transform an exception is thrown. I assume this is a bug.

2) even if you could iterate skus, JSONPath has no parent operator so there would be no way to access the current item .name.

Anyway, that's my best assessment.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tomalak
Solution 2