Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Actual “find” method #21

Open
kizu opened this issue Mar 5, 2013 · 11 comments
Open

Actual “find” method #21

kizu opened this issue Mar 5, 2013 · 11 comments

Comments

@kizu
Copy link

kizu commented Mar 5, 2013

Right now I can see only apply method that returns the values themselves. However if I'd like to manipulate the data in some way, I'd need to know those items' locations.

What I propose is to add something like JSPath.find method, which would get all the things you could throw to the apply method:

JSPath.find(
    '.automobiles{.maker === "Honda" && .year > 2009}.model',
    {
        "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 }
        ],
        "motorcycles" : [{ "maker" : "Honda", "model" : "ST1300", "year" : 2012 }]
    });

But this would return an array of the “steps” which lead to every found element, so in the case above the result would be:

[
    [ "automobiles", 1, "model" ],
    [ "automobiles", 4, "model" ]
]

This way you could then easily find any specific item or it's parent/siblings using any helper functions, so you then could change this item (like in #13) or change anything around it — its parents or siblings.

@dfilatov
Copy link
Owner

dfilatov commented Mar 6, 2013

I will think about it )

@ds82
Copy link

ds82 commented May 22, 2013

Why not use find to return a reference instead of values? Since JS is all about "call-by-reference", one could easily modify those returned references to manipulate the original object.

@dfilatov
Copy link
Owner

jspath returns reference to original object, but this object has no context with it parent/siblings.

@Djemo
Copy link

Djemo commented Jun 13, 2013

+1 for 'find'

@mb21
Copy link

mb21 commented Jul 21, 2014

+1

@AlexFess
Copy link

+1, that would be nice to have

@xogeny
Copy link

xogeny commented Oct 24, 2014

+1

For me, I'd rather have a way to apply a function to the matching data and replace their values, i.e.,

var data = {
        "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 }
        ],
        "motorcycles" : [{ "maker" : "Honda", "model" : "ST1300", "year" : 2012 }]
    };
JSPath.apply('.automobiles{.maker === "Honda" && .year > 2009}.model',
                       data, function(name) { return name+" (found)" });

Which would have the effect of changing data to:

{
        "automobiles" : [
            { "maker" : "Nissan", "model" : "Teana", "year" : 2011 },
            { "maker" : "Honda", "model" : "Jazz (found)", "year" : 2010 },
            { "maker" : "Honda", "model" : "Civic", "year" : 2007 },
            { "maker" : "Toyota", "model" : "Yaris", "year" : 2008 },
            { "maker" : "Honda", "model" : "Accord (found)", "year" : 2011 }
        ],
        "motorcycles" : [{ "maker" : "Honda", "model" : "ST1300", "year" : 2012 }]
}

The existing name apply almost gives the sense that you are applying a transformation. But you aren't. However, I suspect you could actually add the functionality I'm describing above in a backward compatible way (since it just adds an additional, optional argument).

Just a thought.

@formula1
Copy link

@xonegy Well in this case you would want to get the .automobiles object. Javascript returns Objects by reference, so if you set or delete a property of the object, it will update the original.

Consider this example: https://gist.github.com/formula1/07d23b0e3842c7658f8d

Here we only make modifications to res yet it changes j.

As for getting a direct path from point A to point B, it could be nice. mpath is far less powerful than jspath, however it is an example of a direct path from point A to point B.

Sample data "borrowed" from here: http://json.org/example

If you want a cleaner jquery-esque interface, heres a wrapper Object https://gist.github.com/formula1/1c12acb2b21beb3943c7

it needs the JSPath to be in a variable names jspath and you'll want to create a new one when you want to use it. Additionally, its important to get the parent object and not the actual value itself as its almost impossible to backtrace without parsing your string and running jspath a second time with the values enumerated (which is more work than 5 minutes. However you have accessing to chaining like so

new PathWrapper(".a..path{to your things}").apply(myOb).set("a-path", "a-value").delete("uglypath").get() == original values

@bitcloud
Copy link

+1 for getting the path instead of the object or even both like in https://www.npmjs.com/package/jsonpath with jp.nodes

would love to use jspath for that, because I really like the syntax.

@zmorris
Copy link

zmorris commented Apr 26, 2018

+1 I also need paths to found nodes, which JSONPath provides. Unfortunately JSONPath's query syntax can't filter by path and value simultaneously, so JSPath does better there. But without paths, JSPath is relatively useless for traversing the large JSON trees returned by parsers like Babylon.

@zmorris
Copy link

zmorris commented Apr 26, 2018

@dfilatov I went ahead and wrote what I thought was a way to index the json by value -> path so that paths could be looked up for JSPath results. It kind of works but only if every value in the json is a unique reference, which is unlikely in practice:

const JSPath = require('jspath');
const traverse = require('traverse');

// index object as map of each value's reference -> path
function toValuePaths(object) {
	return traverse.reduce(
		object,
		function(accumulator) {
			if (accumulator.get(this.node) !== undefined) {
				console.log("Object's reference is not unique: ", this.path);
				// throw new Error("Object's reference is not unique: " + JSON.stringify(this.path));
			}

			accumulator.set(this.node, this.path);

			return accumulator;
		},
		new Map()
	);
}

// convert JSPath results to {path: ..., value: ...}
function jspathNodes(jspathResults, valuePaths) {
	const isArray = Array.isArray(jspathResults);

	if (!isArray) {
		jspathResults = [jspathResults];
	}

	const nodes = jspathResults.map(function(reference) {
		return { path: valuePaths.get(reference), value: reference };
	});

	return isArray ? nodes : nodes[0];
}

{
	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 }
		],
		motorcycles: [{ maker: 'Honda', model: 'ST1300', year: 2012 }]
	};
	const jsonMap = toValuePaths(json);

	console.log('works (for unique node references):');
	console.log(
		jspathNodes(
			JSPath.apply(
				'.automobiles{.maker === "Honda" && .year > 2009}.model',
				json
			),
			jsonMap
		)
	);
}

console.log();

{
	const json = {
		a: 'hi',
		b: 'hi',
		c: 'hi'
	};
	const jsonMap = toValuePaths(json);

	console.log("fails (node references aren't unique):");
	console.log(jspathNodes(JSPath.apply('..*', json), jsonMap));
}

Output:

Object's reference is not unique:  [ 'automobiles', '2', 'maker' ]
Object's reference is not unique:  [ 'automobiles', '4', 'maker' ]
Object's reference is not unique:  [ 'automobiles', '4', 'year' ]
Object's reference is not unique:  [ 'motorcycles', '0', 'maker' ]
works (for unique node references):
[ { path: [ 'automobiles', '1', 'model' ], value: 'Jazz' },
  { path: [ 'automobiles', '4', 'model' ], value: 'Accord' } ]

Object's reference is not unique:  [ 'b' ]
Object's reference is not unique:  [ 'c' ]
fails (node references aren't unique):
[ { path: [ 'c' ], value: 'hi' },
  { path: [ 'c' ], value: 'hi' },
  { path: [ 'c' ], value: 'hi' } ]

Live demo: https://repl.it/repls/SweetThickProcessors

As you can see, if people really need the paths for results then there is really no way for them to get them themselves. It would be awesome if you could implement a call to return the results as something like [{node: ..., value ...}, ...] similar to the way that JSONPath.nodes() works. Short of that, I think that xogeny's callback implementation would be least intrusive. The callback function should be of the form:

function (value, path, object) {

}

Where the path and original object arguments are optional, which is similar to the Array.map() prototype. Note that there is some consensus on JavaScript object paths as an array of keys, but converting that to a string is somewhat of an open question due to the existence of keys like .. I hope someone solves this because string paths are easier to deal with IMHO.

Other than that, I think JSPath is pretty fantastic and so far is working better for me than JSONPath so thank you for your efforts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants