diff --git a/service.inc b/service.inc
index a1f7f3f..0b0a567 100644
--- a/service.inc
+++ b/service.inc
@@ -466,69 +466,11 @@ class SearchApiSolrService extends SearchApiAbstractService {
       }
 
       // Extract results
-      $results = array();
-      $results['result count'] = $response->response->numFound;
-      $results['results'] = array();
-      foreach ($response->response->docs as $doc) {
-        $doc->id = $doc->item_id;
-        unset($doc->item_id);
-        foreach ($doc as $k => $v) {
-          $result[$k] = $v;
-        }
-        $results['results'][$doc->id] = $result;
-      }
+      $results = $this->extractResults($query, $response);
 
       // Extract facets
-      if (isset($response->facet_counts->facet_fields)) {
-        $results['search_api_facets'] = array();
-        $facet_fields = $response->facet_counts->facet_fields;
-        foreach ($facets as $delta => $info) {
-          $field = $fields[$info['field']];
-          if ($field[0] == 's') {
-            $field = 'f_' . $field;
-          }
-          if (!empty($facet_fields->$field)) {
-            $min_count = $info['min_count'];
-            $terms = $facet_fields->$field;
-            if ($info['missing']) {
-              // We have to correctly incorporate the "_empty_" term.
-              // This will ensure that the term with the least results is dropped, if the limit would be exceeded.
-              $terms = (array) $terms;
-              arsort($terms);
-              if (count($terms) > $info['limit']) {
-                array_pop($terms);
-              }
-            }
-            elseif (isset($terms->_empty_)) {
-              $terms = clone $terms;
-              unset($terms->_empty_);
-            }
-            $type = isset($index->options['fields'][$info['field']]['type']) ? $index->options['fields'][$info['field']]['type'] : 'string';
-            foreach ($terms as $term => $count) {
-              if ($count >= $min_count) {
-                if ($type == 'boolean') {
-                  if ($term == 'true') {
-                    $term = 1;
-                  }
-                  elseif ($term == 'false') {
-                    $term = 0;
-                  }
-                }
-                elseif ($type == 'date') {
-                  $term = isset($term) ? strtotime($term) : NULL;
-                }
-                $term = $term === '_empty_' ? '!' : '"' . $term . '"';
-                $results['search_api_facets'][$delta][] = array(
-                  'filter' => $term,
-                  'count' => $count,
-                );
-              }
-            }
-            if (empty($results['search_api_facets'][$delta]) || count($results['search_api_facets'][$delta]) <= 1) {
-              unset($results['search_api_facets'][$delta]);
-            }
-          }
-        }
+      if ($facets = $this->extractFacets($query, $response)) {
+        $results['search_api_facets'] = $facets;
       }
 
       drupal_alter('search_api_solr_search_results', $results, $query, $response);
@@ -551,6 +493,114 @@ class SearchApiSolrService extends SearchApiAbstractService {
   }
 
   /**
+   * Extract results.
+   *
+   * @param Apache_Solr_Response $response
+   *   A response object from SolrPhpClient.
+   *
+   * @return array
+   *   An array with two keys:
+   *   - 'result count' contains an integer number of total results
+   *   - 'results' is an array of search results; keys are document ids, values
+   *      are arrays of property => value pairs
+   */
+  protected function extractResults(SearchApiQueryInterface $query, Apache_Solr_Response $response) {
+    $index = $query->getIndex();
+    $fields = $this->getFieldNames($index);
+
+    // Set up the results array.
+    $results = array();
+    $results['result count'] = $response->response->numFound;
+    $results['results'] = array();
+
+    // Add each search result to the results array, translating property names
+    // from Solr to Search API as we go. This reverses the mapping in
+    // SearchApiSolrService::getFieldNames() -- note that a Solr property name
+    // may be mapped to multiple Search API property names.
+    foreach ($response->response->docs as $doc) {
+      foreach ($fields as $search_api_property => $solr_property) {
+        if (isset($doc->{$solr_property})) {
+          $result[$search_api_property] = $doc->{$solr_property};
+        }
+      }
+
+      // Use the result's id as the array key. By default, 'id' is mapped to
+      // 'item_id' in SearchApiSolrService::getFieldNames().
+      if ($id = $result['id']) {
+        $results['results'][$id] = $result;
+      }
+    }
+
+    return $results;
+  }
+
+  /**
+   * Extract facets.
+   *
+   * @param Apache_Solr_Response $response
+   *   A response object from SolrPhpClient.
+   *
+   * @return array
+   *   An array describing facets that apply to the current results.
+   */
+  protected function extractFacets(SearchApiQueryInterface $query, Apache_Solr_Response $response) {
+    if (isset($response->facet_counts->facet_fields)) {
+      $index = $query->getIndex();
+      $fields = $this->getFieldNames($index);
+
+      $facets = array();
+      $facet_fields = $response->facet_counts->facet_fields;
+      foreach ($facets as $delta => $info) {
+        $field = $fields[$info['field']];
+        if ($field[0] == 's') {
+          $field = 'f_' . $field;
+        }
+        if (!empty($facet_fields->$field)) {
+          $min_count = $info['min_count'];
+          $terms = $facet_fields->$field;
+          if ($info['missing']) {
+            // We have to correctly incorporate the "_empty_" term.
+            // This will ensure that the term with the least results is dropped, if the limit would be exceeded.
+            $terms = (array) $terms;
+            arsort($terms);
+            if (count($terms) > $info['limit']) {
+              array_pop($terms);
+            }
+          }
+          elseif (isset($terms->_empty_)) {
+            $terms = clone $terms;
+            unset($terms->_empty_);
+          }
+          $type = isset($index->options['fields'][$info['field']]['type']) ? $index->options['fields'][$info['field']]['type'] : 'string';
+          foreach ($terms as $term => $count) {
+            if ($count >= $min_count) {
+              if ($type == 'boolean') {
+                if ($term == 'true') {
+                  $term = 1;
+                }
+                elseif ($term == 'false') {
+                  $term = 0;
+                }
+              }
+              elseif ($type == 'date') {
+                $term = isset($term) ? strtotime($term) : NULL;
+              }
+              $term = $term === '_empty_' ? '!' : '"' . $term . '"';
+              $facets[$delta][] = array(
+                'filter' => $term,
+                'count' => $count,
+              );
+            }
+          }
+          if (empty($facets[$delta]) || count($facets[$delta]) <= 1) {
+            unset($facets[$delta]);
+          }
+        }
+      }
+    }
+  }
+
+  /**
    * Flatten a keys array into a single search string.
    *
    * @param array $keys
