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

toJSON() enters infinite recursion for labelled vectors #424

Open
gadenbuie opened this issue Sep 5, 2023 · 2 comments
Open

toJSON() enters infinite recursion for labelled vectors #424

gadenbuie opened this issue Sep 5, 2023 · 2 comments

Comments

@gadenbuie
Copy link

Reported by a user in rstudio/htmltools#398

A very minimal reprex:

labelled::labelled(1:3) |> jsonlite::toJSON()
#> Error: C stack usage  7955752 is too close to the limit
@mculbert
Copy link

mculbert commented Oct 14, 2023

I ran into this issue, too. I think this is related to the fact that the haven_labelled class inherits from vctrs_vctr.

> x <- haven::labelled(1:10)
> class(x)
[1] "haven_labelled" "vctrs_vctr"     "integer"
> toJSON(x)
Error: C stack usage  7971200 is too close to the limit

As far as I can tell, the asJSON() method for vctrs_vctr removes the vctrs_vctr entry from the class list and then tries to re-dispatch on the amended class list, but when R tries to dispatch a method for c('haven_labelled', 'integer'), it selects the same vctrs_vctr method, causing the infinite loop:

> selectMethod('asJSON', c('haven_labelled', 'integer'))
Method Definition:

function (x, ...)
{
    class(x) <- setdiff(class(x), "vctrs_vctr")
    asJSON(x, ...)
}
<bytecode: 0x7fc74c54eb38>
<environment: namespace:jsonlite>

Signatures:
        x               
target  "haven_labelled"
defined "vctrs_vctr"  

I don't know enough about S3 classes to follow exactly what's going on here, since jsonlite doesn't explicitly register a method for haven_labelled. haven calls setOldClass(c("haven_labelled", "vctrs_vctr")), so maybe that tells R about the inheritance structure and results in the redispatch to asJSON.vctrs_vctr?

In any case, perhaps the fix is in how to pop vctrs_vctr from the class list. Instead of setdiff(class(x), 'vctrs_vctr'), which leaves the subclass (haven_labelled, in this case) in the class list, perhaps something like:

class(x) <- class(x)[-(1:which(class(x) == "vctrs_vctr"))]

which, I believe, would remove vctrs_vctr and all subclasses.

Once I discovered that it was a haven_labelled variable that was causing the infinite loop, it was easy enough for me to convert it to a regular numeric vector before conversion to JSON, but it took a while to figure out that was the source of the issue (I wasn't calling toJSON() directly, and the haven_labelled variable was in a nested data structure—I didn't even initially realize that I wasn't using regular vectors...), and others might not realize the problem when they get an unexpected and unexplained infinite recursion.

Thanks!

@jeroen
Copy link
Owner

jeroen commented Oct 14, 2023

Hmm we put that workaround in place to address #408

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

No branches or pull requests

3 participants