Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # 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

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

No branches or pull requests

3 participants