3

I am having some trouble converting my S4 object back into a list. Take for example, the following nested S4 classes:

setClass("nssItem", 
         representation(value = "numeric", text = "character", prefix = "character", type = "character"),
         prototype(value = as.numeric(NA), text = as.character(NA), prefix = as.character(NA), type = as.character(NA))

         ) 

setClass("geckoNss", representation(absolute = "character", item = "nssItem"))

An object of geckoNss class contains objects of nssItem class. Conceptually this seems like a list-like structure which allows for nesting.

Yet,

> temp <- new("nssItem")
> as.list(temp)
Error in as.list.default(temp) : 
  no method for coercing this S4 class to a vector

I understand this error, that is, I have not actually defined what as.list means or how it applies with respect to the nssItem class. Nonetheless, this seems like a very natural operation. How would I extend the definition of as.list to all new classes I define?

Alex
  • 15,186
  • 15
  • 73
  • 127

2 Answers2

4

This is a second, more general solution. It uses a superclass from which you derive all user-defined classes. Descriptions are in the # comments.

#this is an "empty" superclass that characterises all user-defined classes
setClass("user_defined_class")

#we create an as.list method for this new superclass (this time an S4 method)
setMethod("as.list",signature(x="user_defined_class"),function(x) {
  mapply(function(y) {
    #apply as.list if the slot is again an user-defined object
    #therefore, as.list gets applied recursively
    if (inherits(slot(x,y),"user_defined_class")) {
      as.list(slot(x,y))
    } else {
      #otherwise just return the slot
      slot(x,y)
    }
  },
slotNames(class(x)),
SIMPLIFY=FALSE)
})

setClass("nssItem", 
     representation(value = "numeric",
                    text = "character",
                    prefix = "character",
                    type = "character"),
     prototype(value = as.numeric(NA),
               text = as.character(NA),
               prefix = as.character(NA),
               type = as.character(NA)),
     #note the contains argument that flags the nssItem class as user-defined
     contains="user_defined_class")

setClass("geckoNss",
         representation(absolute = "character", item = "nssItem"),
         #the same for the geckoNss class
         contains="user_defined_class")

Now create one object for each class

temp <- new("nssItem")
tempGecko<-new("geckoNss")

Coerce temp to list

as.list(temp)
#$value
#[1] NA
#
#$text
#[1] NA
#
#$prefix
#[1] NA
#
#$type
#[1] NA

And the tempGecko object

as.list(tempGecko)
#$absolute
#character(0)
#
#$item
#$item$value
#[1] NA
#
#$item$text
#[1] NA
#
#$item$prefix
#[1] NA
#
#$item$type
#[1] NA
cryo111
  • 4,444
  • 1
  • 15
  • 37
  • It's a very beautiful solution! However, it relies on the `item` representation to be of class `nssItem`. This would be a separate question but I require the `item` representation to be a list of objects of `nssItem`. – Alex May 22 '15 at 04:17
1

I am not sure whether I have understood your argument about "nesting" correctly, but here some code on how to extend as.list to your S4 classes. As noted by Alex in the comments, these are actually S3 methods that are used on the S4 objects. This works as well. You can find a nice summary about this topic here Combining S4 and S3 methods in a single function

as.list.nssItem=function(from) mapply(function(x) slot(from,x),
                                      slotNames("nssItem"),
                                      SIMPLIFY=FALSE)

Now let's try as.list on the nssItem object temp (as defined in your post)

as.list(temp)
#$value
#[1] NA
#
#$text
#[1] NA
#
#$prefix
#[1] NA
#
#$type
#[1] NA

Edit: I think I now understand what you mean by nesting. After executing the code above, define a new geckoNss object

tempGecko<-new("geckoNss")

Extend as.list to class geckoNss

as.list.geckoNss=function(from) mapply(function(x) {
  if (x=="item") as.list(slot(from,x)) else slot(from,x)
  },
  slotNames("geckoNss"),
  SIMPLIFY=FALSE)

Now apply as.list to your geckoNss object tempGecko

as.list(tempGecko)

#$absolute
#character(0)
#
#$item
#$item$value
#[1] NA
#
#$item$text
#[1] NA
#
#$item$prefix
#[1] NA
#
#$item$type
#[1] NA

As per Alex comment below, here a more general way of extending as.list.

#save old function definition (just in case...)
as.list.default.save=as.list.default

Define new default method

as.list.default=function(x) {
  if (class(x)=='list') {
    x
  } else if (class(x)%in%c('nssItem','geckoNss')) {
    mapply(function(slot_name) as.list(slot(x,slot_name)),
           slotNames(class(x)),
           SIMPLIFY=FALSE)
  } else {
    .Internal(as.vector(x, "list"))
  }
}

You still have to enter a vector of all your user-defined classes c('nssItem','geckoNss'). I haven't been able to find a function that returns these classes. The result is not as nicely formated as above...

as.list(temp)

#$value
#$value[[1]]
#[1] NA
#
#
#$text
#$text[[1]]
#[1] NA
#
#
#$prefix
#$prefix[[1]]
#[1] NA
#
#
#$type
#$type[[1]]
#[1] NA
Community
  • 1
  • 1
cryo111
  • 4,444
  • 1
  • 15
  • 37
  • Thanks for this answer. It is very helpful to actually let me extend `as.list` to these two objects and also convert the nested s4 objects into nested lists. I think the same unpackaging concept could extend to other new classes, but I don't want to have to write as `as.list` method for all of them. That is what I meant by "canonical" in the question title. – Alex May 22 '15 at 01:16
  • I have another question, can you use the `as.list.` method to define new methods on s4 classes? I thought this was a S3 way of defining a method – Alex May 22 '15 at 01:40
  • @Alex When R does not find an S4 method it uses the S3 method (if available). You can also define an S4 method. – cryo111 May 22 '15 at 02:04
  • Excellent, the newest edit is getting the function to where I would like it. Would we be able to use `rapply` to preserve the nested structure? I am interested in how to obtain all available classes as well: http://stackoverflow.com/questions/30363558/how-do-i-see-existing-classes – Alex May 22 '15 at 02:34
  • @Alex I think I have found a better solution now. I will post it in a separate answer. – cryo111 May 22 '15 at 02:37