protected function _expandValue($active_ctx, $active_property, $value) {
    // nothing to expand
    if($value === null) {
      return null;
    }

    // special-case expand @id and @type (skips '@id' expansion)
    $expanded_property = $this->_expandIri(
      $active_ctx, $active_property, array('vocab' => true));
    if($expanded_property === '@id') {
      return $this->_expandIri($active_ctx, $value, array('base' => true));
    } else if($expanded_property === '@type') {
      return $this->_expandIri(
        $active_ctx, $value, array('vocab' => true, 'base' => true));
    }

    // get type definition from context
    $type = self::getContextValue($active_ctx, $active_property, '@type');

    // do @id expansion (automatic for @graph)
    if($type === '@id' || ($expanded_property === '@graph' &&
      is_string($value))) {
      return (object)array('@id' => $this->_expandIri(
        $active_ctx, $value, array('base' => true)));
    }
    // do @id expansion w/vocab
    if($type === '@vocab') {
      return (object)array('@id' => $this->_expandIri(
        $active_ctx, $value, array('vocab' => true, 'base' => true)));
    }

    // do not expand keyword values
    if(self::_isKeyword($expanded_property)) {
      return $value;
    }

    $rval = new stdClass();

    // other type
    if($type !== null) {
      $rval->{'@type'} = $type;
    } else if(is_string($value)) {
      // check for language tagging for strings
      $language = self::getContextValue(
        $active_ctx, $active_property, '@language');
      if($language !== null) {
        $rval->{'@language'} = $language;
      }
    }
    $rval->{'@value'} = $value;

    return $rval;
  }