XingFramework/Relayer

View on GitHub
src/relayer/jsonpath.js

Summary

Maintainability
F
2 wks
Test Coverage
/* JSONPath 0.8.0 - XPath for JSON
 *
 * Copyright (c) 2007 Stefan Goessner (goessner.net)
 * Licensed under the MIT (MIT-LICENSE.txt) licence.
 */

if (!Array.isArray) {
  Array.isArray = function(vArg) {
    return Object.prototype.toString.call(vArg) === "[object Array]";
  };
}

var cache = {};

function push(arr, elem) { arr = arr.slice(); arr.push(elem); return arr; }
function unshift(elem, arr) { arr = arr.slice(); arr.unshift(elem); return arr; }

export default function jsonPath(obj, expr, arg) {
   var P = {
      resultType: arg && arg.resultType || "value",
      flatten: arg && arg.flatten || false,
      wrap: (arg && arg.hasOwnProperty('wrap')) ? arg.wrap : true,
      sandbox: (arg && arg.sandbox) ? arg.sandbox : {},
      normalize: function(expr) {
         if (cache[expr]){ return cache[expr]; }
         var subx = [];
         var normalized = expr.replace(/[\['](\??\(.*?\))[\]']/g, function($0,$1){return "[#"+(subx.push($1)-1)+"]";})
                     .replace(/'?\.'?|\['?/g, ";")
                     .replace(/(;)?(\^+)(;)?/g, function(_, front, ups, back) { return ';' + ups.split('').join(';') + ';'; })
                     .replace(/;;;|;;/g, ";..;")
                     .replace(/;$|'?\]|'$/g, "");
         var exprList = normalized.split(';').map(function(expr) {
            var match = expr.match(/#([0-9]+)/);
            return !match || !match[1] ? expr : subx[match[1]];
         });
         cache[expr] = exprList;
         return exprList;
      },
      asPath: function(path) {
         var x = path, p = "$";
         for (var i=1,n=x.length; i<n; i++){
            p += /^[0-9*]+$/.test(x[i]) ? ("["+x[i]+"]") : ("['"+x[i]+"']");
         }
         return p;
      },
      trace: function(expr, val, path) {
         function addRet(elems) { ret = ret.concat(elems); }
         // no expr to follow? return path and value as the result of this trace branch
         if (!expr.length){ return [{path: path, value: val}]; }

         var loc = expr[0], x = expr.slice(1);
         // the parent sel computation is handled in the frame above using the
         // ancestor object of val
         if (loc === '^'){
           if(path.length){
             return [{path: path.slice(0,-1), expr: x, isParentSelector: true}];
           } else {
             return [];
           }
         }

         // we need to gather the return value of recursive trace calls in order to
         // do the parent sel computation.
         var ret = [];

         if (val && val.hasOwnProperty(loc)) { // simple case, directly follow property
            addRet(P.trace(x, val[loc], push(path, loc)));
         } else if (loc === "*") { // any property
            P.walk(loc, x, val, path, function(m,l,x,v,p) {
               addRet(P.trace(unshift(m, x), v, p)); });
         }
         else if (loc === "..") { // all chid properties
            addRet(P.trace(x, val, path));
            P.walk(loc, x, val, path, function(m,l,x,v,p) {
               if (typeof v[m] === "object") {
                  addRet(P.trace(unshift("..", x), v[m], push(p, m)));
               }
            });
         }
         else if (loc[0] === '(') { // [(expr)]
            addRet(P.trace(unshift(P.evaluate(loc, val, path[path.length], path),x), val, path));
         }
         else if (loc.indexOf('?(') === 0) { // [?(expr)]
            P.walk(loc, x, val, path, function(m,l,x,v,p) {
               if (P.evaluate(l.replace(/^\?\((.*?)\)$/,"$1"),v[m],m, path)) {
                  addRet(P.trace(unshift(m,x),v,p));
               }
            });
         }
         else if (loc.indexOf(',') > -1) { // [name1,name2,...]
            for (var parts = loc.split(','), i = 0; i < parts.length; i++) {
               addRet(P.trace(unshift(parts[i], x), val, path));
            }
         }
         else if (/^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/.test(loc)) { // [start:end:step]  python slice syntax
            addRet(P.slice(loc, x, val, path));
         }

         // we check the resulting values for parent selections. for parent
         // selections we discard the value object and continue the trace with the
         // current val object
         return ret.reduce(function(all, ea) {
            return all.concat(ea.isParentSelector ? P.trace(ea.expr, val, ea.path) : [ea]);
         }, []);
      },
      walk: function(loc, expr, val, path, f) {
         if (Array.isArray(val)) {
            for (var i = 0, n = val.length; i < n; i++) {
               f(i, loc, expr, val, path);
            }
         } else if (typeof val === "object") {
            for (var m in val) {
               if (val.hasOwnProperty(m)) {
                  f(m, loc, expr, val, path);
               }
            }
         }
      },
      slice: function(loc, expr, val, path) {
        if (!Array.isArray(val)){ return; }
        var len = val.length, parts = loc.split(':'),
             start = (parts[0] && parseInt(parts[0])) || 0,
             end = (parts[1] && parseInt(parts[1])) || len,
             step = (parts[2] && parseInt(parts[2])) || 1;
        start = (start < 0) ? Math.max(0,start+len) : Math.min(len,start);
        end   = (end < 0)   ? Math.max(0,end+len)   : Math.min(len,end);
        var ret = [];
        for (var i = start; i < end; i += step) {
          ret = ret.concat(P.trace(unshift(i,expr), val, path));
        }
        return ret;
      },
      evaluate: function(code, _v, _vname, path) {
        if( false ) { //supporting this requires allowing JS eval - very dodgy
          if (!$ || !_v){ return false; }
          if (code.indexOf("@path") > -1) {
            P.sandbox["_path"] = P.asPath(path.concat([_vname]));
            code = code.replace(/@path/g, "_path");
          }
          if (code.indexOf("@") > -1) {
            P.sandbox["_v"] = _v;
            code = code.replace(/@/g, "_v");
          }
          try {
            return vm.runInNewContext(code, P.sandbox);
          }
          catch(e) {
            console.log(e);
            throw new Error("jsonPath: " + e.message + ": " + code);
          }
        } else {
          var msg = `Refusing to eval: '${code}'`;
          console.log(msg);
          throw new Error(`jsonPath: ${msg}`);
        }
      }
   };

   var $ = obj;
   var resultType = P.resultType.toLowerCase();
   if( obj ){
     if (expr  && (resultType == "value" || resultType == "path")) {
       var exprList = P.normalize(expr);
       if (exprList[0] === "$" && exprList.length > 1){
         exprList.shift();
       }
       var result = P.trace(exprList, obj, ["$"]);
       result = result.filter(function(ea) { return ea && !ea.isParentSelector; });
       if (!result.length){
         if(P.wrap){
           return [];
         } else {
           return false;
         }
       }

       if (result.length === 1 && !P.wrap && (resultType == 'path' || !Array.isArray(result[0].value))) {
         var ret = result[0][resultType];
         if(typeof ret === "undefined"){
           return false;
         } else {
           return ret;
         }
       }
       return result.reduce(function(result, ea) {
         var valOrPath = ea[resultType];
         // For our purposes we don't return paths but rather arrays of segments
         // if (resultType === 'path'){ valOrPath = P.asPath(valOrPath); }
         if (P.flatten && Array.isArray(valOrPath)) {
           result = result.concat(valOrPath);
         } else {
           result.push(valOrPath);
         }
         return result;
       }, []);
     }
   } else {
     return obj;
   }
}