-
Notifications
You must be signed in to change notification settings - Fork 0
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
Get and set object values by path #22
Comments
Here are some of my thoughts on everything above:
I also think these are the simplest applications of getting and setting, just getting and setting arbitrary nodes, so I'm on board with
I like this so that we're not returning a single node in the generic case and an array in the case where wildcards/other more complex operations were involved.
I think by default set should only set paths if they exist, including the case where a wildcard/other expression comes into play, then it should set all matching and existing paths. Then we can allow an optional param, something along the lines But now that I'm thinking about it, setting a non-existent path is challenging because we do not know for sure how to add nodes to their tree structure. For example, what if their node is a custom class? We couldn't simply create objects/properties to create this path. So we'll have to think about this case more. Maybe this is a sign that this may be a use case that we should wait on verifying that we need need to support it?
I like the idea of having this since it provides flexibility to add new operations and features in the future, and would allow us to start with simple and common usecases first and add new ones as they arise |
Let’s start simple. Paths are arrays with values:
Thoughts? |
Wrt setting, the idea is we'd use |
I like it, it's simple and easy to understand |
@LeaVerou A few more clarifying points on
Just wanted to get your opinion on these things, for 1-3, my answer would be yes, it should do those things, and for 4, I would lean toward returning |
After thinking about this some more, I wonder if we could get rid of all this complexity and just have an array of properties that point to one or more children. The nodes that have a single |
I like this idea much better than having a wildcard operator and all the complex syntax for defining it versus |
So if I'm understanding your idea correctly, For |
It means |
@adamjanicki2 what happened with this? Being able to set how to get from parent to children in a more generic way is pretty essential. |
What does this mean? Are you referring to general |
Background
This came up in #18 as an implementation detail, but I think it's incredibly useful in its own right.
Sample use cases and prior art:
Mavo.subset()
set()
andunset()
functions (and their various permutations)get()
$.value()
I pushed two util functions a few days ago (
getByPath()
andsetByPath()
but I think we should clean up the code, make it more flexible, and expose at the top level.Some design decisions, requirements, questions below.
Signature
Function names
I’m leaning towards the simple
get()
andset()
that are already established in prior work. Is there anything else we may want to save the namesget()
andset()
for?Arguments
Strawman:
get(obj, path [, options])
set(obj, path, value [, options])
Might be worth to later have overloads that allow specifying
value
,path
as part of the options object, but don't see compelling motivation to include that in the MVP.Should there be a way to set
value
via a function that takes the path and current value as parameters? Or is it encroaching too much intotransform()
territory at this point?Path structure
Data type
Paths should be provided as arrays, we don’t want to deal with string parsing and trying to distinguish paths from property names. Strings/numbers should be accepted as well, but they’re just a path of length 1.
We may want to also support objects to provide additional metadata (see below).
Predicates
It seems obvious that entirely literal paths will not suffice (at the very least we need wildcards).
Should we just use JSON Path? Hell no! First it's overkill for these use cases, and second once you go beyond literal property names + wildcards, the syntax becomes cryptic AF. And despite its complexity, there are some pretty common use cases like case insensitivity it doesn’t seem to support.
So since we can’t just use JSON Path, what do we use? What predicates do we want to support? Examples:
:has()
, so we'd probably want to frame it that way, i.e. "children that match this path", so I’ll call them child queries from now onWe generally want to keep the MVP simple until use cases emerge, but it helps to take these things into account at the design stage so that the API has room to expand.
As mentioned above, wildcards are certainly needed.
Case-insensitive matching might be worth to include in the MVP, since at least the Mavo use cases need it.
The rest we can probably ship without and add as needed.
Syntax for predicates
So that begs the question, how do we express these predicates?
Special syntax. This works decently for some of them:
*
foo|bar
0-3
or0 .. 3
id=foo
However, but there is no obvious fit for any of the others. Also, inventing a new microsyntax has several drawbacks:
"\*"
property then? More backslashes? It's backslashes all the way down!id=foo
and I now think that's a terrible idea and we dropped that kind of support fromget()
(it's now only supported inmv-path
, which being an HTML attribute it only takes strings so it can't take anything more structured).So instead, I think we should go with an approach of strings for literals + wildcards as the only exception, since these are very common and have a very obvious syntax. Anything else would require making that part of the path an object literal.
This means even if we only ship wildcard as the only predicate, we need to support object literals at least to escape that and specify that something is a literal property name. If we have that escape hatch, we could in the future explore more options to add syntax for certain things where a readable syntactic option is obvious, as a shortcut (e.g.
"foo|bar"
for alternatives)Predicate schema
Strawman for all of the above predicates (even though we don't plan to implement them all):
string | (string | PathSegment)[]
name
: Literal property name (string
) but maybe could also be aRegExp
?ignoreCase
(boolean
)range
: Numerical range (number[2]
or{from, to}
or even{gt, gte, lt, lte}
?)or
: Alternatives ((string | PathSegment[])
)has
: Return only children for which this would be non-empty (Path
)startsWith
endsWith
regexp
Notes:
ignoreCase
is special. All other criteria are independent, butignoreCase
affects how other criteria work, i.e. is a modifier rather than a predicate:name
: from strict equality to equality after.toLowerCase()
regexp
: Adds thei
flag if not presentstartsWith
/endsWith
: applies.toLowerCase()
before matchingor
andhas
: inherits to any path segments that don't have their ownignoreCase
or
complex logical criteria can be created by just nesting these. 😁{or: array}
?How do predicates work with
set()
?Setting is only an issue for the last part of the path — until then it's still a getting task.
So if the last part of the path is a…
Return value
Following the design principle that function return values should not vary wildly based on the options passed, perhaps we actually need more than just a single
get()
function:get()
: Array of all valuesfirst()
: First value onlysubset()
: Subset of objectOr perhaps
get()
for one value andgetAll()
for multiple?Options for the whole path
These will be passed to the functions as part of the
options
dictionary.set()
only: What object to create when part of the path doesn’t exist?{}
by default. Might be useful to take a function to customize based on the path.The text was updated successfully, but these errors were encountered: