damnhandy / Handy-URI-Templates

A Java URI Template processor implementing RFC6570

Home Page:https://damnhandy.github.io/Handy-URI-Templates/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Nested data structures

mmadson opened this issue · comments

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

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.

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!

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.

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:

  • The operator is only defined at the top level. Thus, every nested data structure would have to reuse the operator of its parent.
  • If it does this, it ends up forcing URL encoding on the rendered parts
  • You have no means of controlling the explode modifier on the nested parts
  • There is no way in the spec to associate on part as the label and the nested array as the set of values.

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.

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.

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

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.