damnhandy / Handy-URI-Templates

A Java URI Template processor implementing RFC6570
https://damnhandy.github.io/Handy-URI-Templates/
Other
203 stars 37 forks source link

Nested data structures #27

Closed mmadson closed 8 years ago

mmadson commented 9 years ago

I realize nested data structures are not currently supported, are there any plans?

class QueryParams {
  List<String> field;
  List<String> user;

  ... ctor, getters and setters elided ... 
}

UriTemplate.fromTemplate("/test{?queryParams*}")
    .set("queryParams", new QueryParams(Lists.newArrayList("a","b","c"), Lists.newArrayList("1","2","3")))
    .expand();

Expected:

/test?field=a&field=b&field=c&user=1&user=2&user=3

OR

/test?field=a,b,c&user=1,2,3

Selecting one form or the other could perhaps be an annotation on the composite, for example:

class QueryParams {
  @ValueExpansion(CSV)
  List<String> field;
  @ValueExpansion(MULTIPARAM)
  List<String> user;

  ... ctor, getters and setters elided ... 
}

might yield:

/test?field=a,b,c&user=1&user=2&user=3
damnhandy commented 9 years ago

Sorry, but no, I was not planning on adding this to the core. This is mainly due to the fact that the spec doesn't cover this and it presents challenges with interoperability.

mmadson commented 9 years ago

Forgive my presumptuousness, but please allow me to elaborate my interpretation of Section 2.4.2 of the spec:

An explode ("*") modifier indicates that the variable is to be treated as a composite value consisting of either a list of values or an associative array of (name, value) pairs. Hence, the expansion process is applied to each member of the composite as if it were listed as a separate variable

For my example of expanding query variables above, we are dealing with an associative array of (name,value) pairs:

[("field",["a","b","c"]),("user",[1,2,3])]

The interesting bit to me, though, is that each member of the composite should be treated as if it were listed as a separate variable. So my interpretation is that the variable expansion process should be repeated for each entry in the associative array as if the template had been listed like so:

{?field*}{&user*}

Where both field and user are list template variables.

An explode modifier applied to a list variable causes the expansion to iterate over the list's member values. For path and query parameter expansions, each member value is paired with the variable's name as a (varname, value) pair.

So if my earlier interpretation holds and we repeat the expansion process for each member of the composite, we encounter 2 list variables and according to this section, we should pair each value in the list with the variables' name.

So the result should be

?field=a&field=b&field=c&user=1&user=2&user=3.

I'll admit that the spec definitely doesn't make this clear and it's entirely possible that I'm not reading things correctly, but at the very least I would have expected to be able to supply a custom VarExploder to handle this use case.

If I have time I might try to fork and add this functionality, but I'm curious what your concerns regarding interoperability are so I don't end up wasting my time.

Thanks!

damnhandy commented 9 years ago

I'd consider this, but I'd suggest issue a PR to the uritemplate-test project which maintains the standard set of test cases for RFC6570. If this is a use case that can be supported by multiple uritemplate processors, I'd definitely add it. But I'd like to avoid adding a custom addition to the spec that only works with this library.

damnhandy commented 8 years ago

So looking at this more, this isn't going to work with the spec itself. The scenario you've posted wouldn't render the URI you desire. Given:

[("myfield",["a","b","c"]),("myperson",[1,2,3])]

And a template of:

{?field*}{&user*}

You would get something like this:

/?field=myfield&field=field%3Da%26field%3Db%26field%3Dc&user=myperson&user=user%3D1%26user%3D2%26user%3D3

There's a few problems with the spec and nested data structures such as:

However, it should be possible to do this with a VarExploder. The interface is pretty basic as all that it does is force your to expose your data structure as key/value pairs of Strings.

damnhandy commented 8 years ago

For an example of a custom VarExploder, check out this test case:

https://github.com/damnhandy/Handy-URI-Templates/blob/master/src/test/java/com/damnhandy/uri/template/TestCustomVarExploder.java

and the example implementation:

https://github.com/damnhandy/Handy-URI-Templates/blob/master/src/test/java/com/damnhandy/uri/template/JsonVarExploder.java

The JsonVarExploder doesn't do anything fancy, it just converts the JSON into a Map. with a little effort, the JsonVarExploder could be tweaked to satisfy your requirements.

damnhandy commented 8 years ago

BTW, I seem to have missed your annotation idea when I first read this. That is something worth looking into. Stay tuned...

damnhandy commented 8 years ago

I've actually implemented this. You can find the test case here:

https://github.com/damnhandy/Handy-URI-Templates/blob/master/src/test/java/com/damnhandy/uri/template/TestNestedDataStructures.java

The output you can expect for your initial test case would be:

/test?field=a,b,c&user=1,2,3

but not this format:

/test?field=a&field=b&field=c&user=1&user=2&user=3

This is now available in the 2.1.5-SNAPSHOT. I'll release 2.1.5 later in the week once I get a few more negative test cases in place.