Coyote's Guide to IDL Programming

Extracting Object Fields

QUESTION: I would like to write a general extraction method for IDL objects. But, unfortunately Tag_Names doesn't work for objects, only structures. Is there a way to write a general extraction method for objects that can return any field of the object?

ANSWER: Even though objects are very much like structures, routines such as Tag_Names don't work with them. It is possible, however, to get to the underlying structure of an object and extract its fields in a general way.

In the little example program below I use the Obj_Class function to return the class name of the object. This is essentially the name of the structure from which this object was created. But the name is in the form of a string variable. To turn that into a real structure, from which I can extract the field in question, I use the Execute command to "build" the structure de novo. The code looks like this:

   FUNCTION OBJECT::EXTRACT, field

   ; Check if "field" is a valid field name. If it is,
   ; return the value of the field. If not, return -1.

      ; Make sure "field" is a string variable.

   s = Size(field)
   IF s[s[0]+1] NE 7 THEN Message, "Field variable must be a sting."

      ; Get the name of the object class.

   thisClass = Obj_Class(self)

      ; Create a local structure of this type.

   ok = Execute('thisStruct = {' + thisClass + '}')

      ; Find the field identifier (index) in the local structure.

   structFields = Tag_Names(thisStruct)
   index = WHERE(structFields EQ StrUpCase(field), count)

      ; Extract and return the field if it is found.

   IF count EQ 1 THEN BEGIN
      RETURN, self.index(0) 
   ENDIF ELSE BEGIN
      Message, 'Can not find field "' + field + $
          '" in structure.', /Informational
      RETURN, -1
   ENDELSE
   END

To find, for example, the field "range" in this object, you would type this:

   thisRange = Object->Extract("range")

J.D. Smith of Cornell University offered suggestions that made this example program even more simple and elegant.

Several months after I first wrote this article, Bob Mallozzi of the NASA Marshal Space Flight Center wrote to me with some further helpful suggestions. I include his entire e-mail to me here because I think it illustrates extremely well the evolution of good programs. I personally think his suggestions are excellent, especially his point about not violating the encapsulation rules of objects.

Date Sat, 11 Apr 1998 084758 -0500 (CDT)
From "Robert S. Mallozzi"
Subject Object fields

Hello David,

I was just looking at one of the helpful tips you have posted on your web page entitled "Extracting Object Fields." I wanted to mention a couple things regarding the code  you have there.  The part of the code is the following:

   structFields = Tag_Names(thisStruct)
   index = WHERE(structFields EQ StrUpCase(field), count)
   
         ; Extract and return the field if it is found.

   IF count EQ 1 THEN BEGIN
      RETURN, self.index(0) 
   ENDIF ELSE BEGIN
      Message, 'Can not find field "' + field + $
          '" in structure.', /Informational
      RETURN, -1
   ENDELSE
 

You are returning the value "self.index(0)", but the tag name "index" is not the tag name that we want for this object.  Another EXECUTE will solve the problem.

   structFields = Tag_Names(thisStruct)
   index = WHERE(structFields EQ StrUpCase(field), count)

      ; Extract and return the field if it is found.

   IF (count EQ 1) THEN BEGIN

      IF (EXECUTE ('retVal = self.' + structFields[index[0]])) THEN BEGIN

         ; Don't ever return a valid pointer into an object
	;
	IF (PTR_VALID (retVal)) THEN BEGIN
	    RETURN, *retVal
	ENDIF ELSE BEGIN   
	    RETURN, retVal
         ENDELSE
	
      ENDIF

      RETURN, -1

   ENDIF ELSE BEGIN

      Message, 'Can not find field "' + field + $
          '" in object ' + OBJ_CLASS (self), /Informational
      RETURN, -1

   ENDELSE
 

Also, there is something else I would like to point out.  Notice the check in the code above to make sure we dereference a pointer before returning it.   One should never return a valid pointer into to an object, as that breaks encapsulation by allowing access to the object's internal data without going through a defined object method.  For example, by returning a (non-null) pointer one can do the following type of things.

    IDL> o = OBJ_NEW ('example')    
    IDL> o->PRINT   
    instance_data_int =       10
    instance_data_float =       20.0000
    instance_data_ptr =       30.0000
    IDL> myPtr = o->extract ('instance_data_ptr')
    IDL> PRINT, *myPtr
          30.0000
    IDL> o->PRINT
    instance_data_int =       10
    instance_data_float =       20.0000
    instance_data_ptr =       30.0000
    IDL> *myPtr = 100
    IDL> o->PRINT
    instance_data_int =       10
    instance_data_float =       20.0000
    instance_data_ptr =      100
 

Here, I actually changed the datatype of the object's instance data from outside the object.

     

Regards,

Robert S. Mallozzi

Google
 
Web Coyote's Guide to IDL Programming