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

How to pass length-1 vector as vector? #27

Closed
YStrauchP4 opened this issue Dec 2, 2022 · 7 comments
Closed

How to pass length-1 vector as vector? #27

YStrauchP4 opened this issue Dec 2, 2022 · 7 comments
Labels
documentation Improvements or additions to documentation
Milestone

Comments

@YStrauchP4
Copy link

Hi!

R has this weird quirk that everything is a vector, even strings.

If my template uses a for loop on a vector, it succeeds if there's more than one entry but crashes if its length is just one, I guess some internal logic casts it to a string/number instead of an iterable with one element. This makes my templates crash randomly and I had to implement a helper function to manually cast vectors of length one to a list when I need to iterate over it. That's quite ugly...

I'm not sure if you can do anything about this, I guess you need to guess the data types and convert them for the C++ data types and you can't know if someone wants to iterate over a vector or use it as a string, but I thought I leave a ticket as this is an annoying caveat of R. Maybe add some documentation if you can't fix it haha

@davidchall
Copy link
Owner

Hi @YStrauchP4,

You're absolutely right that a length-1 vector is automatically converted to a scalar to be used by the template engine. We needed to choose a default behavior for this ambiguous scenario, and this was the most common usage pattern. This behavior is documented here.

However, I agree there are many cases where you want to pass a vector. And that vector will sometimes have 1 element! 😄 Fortunately, you can explicitly request this behavior using the I() function (aka the "as-is" operator).

library(jinjar)

template = "{% for person in people -%}
Hello {{ person }}
{%- endfor -%}"

# vector with 2 elements
render(template, people = c("Woody", "Buzz")) |> writeLines()
#> Hello Woody
#> Hello Buzz

# vector with 1 element
render(template, people = c("Andy")) |> writeLines()
#> Error in `render()` at jinjar/R/render.R:37:2:
#> ! Problem encountered while rendering template.
#> Caused by error:
#> ! Object must be an array.
#> ℹ Error occurred on line 1 and column 15.

# explicitly prevent conversion to scalar
render(template, people = I(c("Andy"))) |> writeLines()
#> Hello Andy

Created on 2022-12-02 with reprex v2.0.2

I totally agree this feature needs to be documented though - I'll add this in the next release. Thanks for the report!

@davidchall davidchall added the documentation Improvements or additions to documentation label Dec 2, 2022
@davidchall davidchall changed the title Vector handling How to pass length-1 vector as vector? Dec 2, 2022
@davidchall davidchall added this to the 0.3.1 milestone Dec 2, 2022
@YStrauchP4
Copy link
Author

Thanks for your quick response!
Using the as-is operator is certainly less ugly than my helper function, wasn't aware of this operator and will certainly use it instead! Thanks for adding it to the docs!

@33Vito
Copy link

33Vito commented Feb 28, 2023

Hi David,

Great package! Love it as a long-time Jinja user, really hope more features will become available (e.g. the '|' filter feature in Python)

In this case, I just want to check if there is a way for this to work with a list input loaded from external? In my workflow, I use a YAML file to contain all the parameters required by the Jinja template. The YAML object loaded into R becomes a deeply nested list.

This following will work

render(template, !!!list(people = I(c("Andy")))) |> cat()

But I was looking for a way to define the parameters completely inside YAML so R does not have to mess with it.

@davidchall
Copy link
Owner

Hi @33Vito - So in your use case, you want to specify that all length-1 vectors should be treated as vectors. Is that right?

@33Vito
Copy link

33Vito commented Mar 1, 2023

Hi @33Vito - So in your use case, you want to specify that all length-1 vectors should be treated as vectors. Is that right?

Yes

@davidchall
Copy link
Owner

davidchall commented Mar 6, 2023

Hi @33Vito - you can apply I() to every value in your deeply nested list using purrr::modify_tree().

Here's an example, demonstrating that field1 and field3 are both treated as vectors.

library(jinjar)

# data stored in nested list (read from YAML)
data <- list(field1 = "value1", field2 = list(field3 = "value3"))

# wrap every value in I() to preserve vectors
data2 <- purrr::modify_tree(data, leaf = I)

# inspect data structures
str(data)
#> List of 2
#>  $ field1: chr "value1"
#>  $ field2:List of 1
#>   ..$ field3: chr "value3"
str(data2)
#> List of 2
#>  $ field1: 'AsIs' chr "value1"
#>  $ field2:List of 1
#>   ..$ field3: 'AsIs' chr "value3"

# render example template
template <- "
{% for val in field1 %}{{ val }}{% endfor %}
{% for val in field2.field3 %}{{ val }}{% endfor %}
"

render(template, !!!data2)
#> [1] "\nvalue1\nvalue3\n"

Created on 2023-03-06 with reprex v2.0.2

@33Vito
Copy link

33Vito commented Mar 6, 2023

This is awesome. Thanks, mate.

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

No branches or pull requests

3 participants