Open rob42 opened 10 years ago
Thanks :)
I think it's probably better to use JSON Pointer syntax for this sort of thing. There's already a private method that resolve a JSON pointer string. One reason I haven't exposed it as a public is just I haven't thought it through much: what's a nice suggestive yet short name? Are there other similar operations that need to be considered and that should work consistently with it (e.g. pattern matching)? How to deal with missing properties...throw an exception or return null or Json.nil()?
Ahh, yes, Json pointer achieves the same and is more std. Silly of me. Plus it fits nicely with a general REST interface to the signalK model.
Why not just overload the 'at' method? Test for / and split if it exists? Would have a slight performance penalty. Otherwise 'for','get', or 'atPath'?
A json-path implementation (http://goessner.net/articles/JsonPath/) would be nice too :-)
Right, except that / is a valid JSON string, and therefore object key, character so there is ambiguity with names that start with /. So it has to be another method name. Since it's to be another method name, and we want to be interpreting some expression syntax to retrieve an element elsewhere in the tree, that begs the question shouldn't it be a specific case of a more general path/pattern search function?
I've seen JSONPath before and I've thought of it. Thanks for reminding me. I saw it a couple of year ago and though that soon it or something like it would be picked up by a standard body. Unfortunately this hasn't happened.
Maybe the right way forward for mJson is to provide an interface to plugin the evaluation of expression within the methods that already exist. For example, while / is a valid string character, it's probably quite rare that it's actually used as part of property names. Or if it is, maybe the dot . character is not used. The Json.Factory interface is already all one needs to change the semantics of JSON object construction and the implementation of JSON entities, entirely...so something similar could take care of the "retrieving information" portion...
A factory approach works but drops the user into working on mJson extensions (set up project, learn codebase, build,maintain, etc), taking attention away from the real goal, in my case SignalK.
How about adding a single additional factory method 'setPathSeparator(char)' By default its null, when set its used in the 'at()' commands to split. That would allow user control with minimal complexity, and fulfil both '/' and '.' cases. Would still need an 'at(String[])', then it would suit all my usecases for now, and probably 90% of likely cases.
The problem I see with JsonPointers is no ability to do wildcards or xpath tests, eg //address/*[city=Nelson]. So JsonPath wins for that.Maybe factory.setJsonPathEvaluation(boolean) would add that? Combined with 'setPathSeparator' would allow both '//address' and '..address' forms of statement. While there may be validity conflicts for some json, the user of mJson would know this when they enabled it, and allow for that when engaging those capabilities?
In my use cases I dont currently see the need for complex jsonPaths, just the // and * would suffice
I'm talking more about defining a new interface, some along the lines of:
interface ExpressionEvaluator { Json eval(String expression, Json object) }
with a default implementation that could perhaps accept some configuration options. So no need to setup project and customize. This way, the interface of Json itself remains small. Otherwise, methods like set this set that lead to a really bloated interface. I've also thought of having some general configuration Json object so you could set options with Json.opt(name, value).
But the main point is that if, after some time, a standard emerges that mJson is going to support without breaking backward compatibility for existing code bases, changing behavior should be a matter of setting up the right evaluator at application startup time. I'm just not sure what that ExpressionEvaluator interface should look like....:)
Incidentally, you can already override the 'at' method to do what you want. You need to do the following:
class MyFactory extends Json.DefaultFactory { Json object() { // return your own implementation of a Json object with the at method interpreting a path like expression the way you want it } }
But I agree that seems like too much work.
In any case, for JSON PAth, certainly a new method or even more is needed. For one thing, since the result of a path is not necessarily a single element, but it actually may be a large number of them, we'd have to return a result-set-like object, e.g. Iterable.
I really can't think of a better alternative than a general method that evaluates string expressions on a Json and returns the result. Both JSON Pointer and JSONPath are at the end of the day small expression languages that operate on JSON structures. Something that allows transformation would be yet another such expression language. Of course eval(String) is pretty much as general as one can get since everything can be fit under the "evaluate an arbitrary string" abstraction, but I can hardly see the point of having separate methods like "atPath" "pathQuery" "transform". Also, doing the parsing only once is certainly going to be an important performance boost in many cases. So what is needed is an generic interface for expression evaluation that can be plugged into mJson:
public class Json { public static interface Evaluator { Expression compile(String expression, Json...options); }
public static interface Expression { Json eval(Json data); }
public Json eval(String expresion, Json...options) { return getEvaluator().compile(expression, options).eval(this); } }
so one can plug an expression evaluator. A default one would be provided which would detect if we are dealing JSON Pointer or JSONPath and do the right thing.
Nice work on mJson, I like the concept. Been trying it out and I found I added these pretty quick for big trees. Typing at("person").at(".address").at("city")... gets quite tedious :-)
/* * Get the Json node at "person.address.city" * @param path * @return / public Json atPath(String path){ String[] paths = path.split("."); return atPath(paths); } / * Get the json node at {"person","address","city"} * Convenient when using CONSTANTS, atPath(PERSON,ADDRESS,CITY) * @param path * @return */ public Json atPath(String ... path ){ Json json=null; for(String k:path){ if(json==null)json=at(k); json=json.at(k); if(json==null) return null; } return json; }