diff --git a/lib/Drupal/views/Plugin/views/display/Json.php b/lib/Drupal/views/Plugin/views/display/Json.php
new file mode 100644
index 0000000..61c5f47
--- /dev/null
+++ b/lib/Drupal/views/Plugin/views/display/Json.php
@@ -0,0 +1,246 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\views\Plugin\views\display\JSon.
+ */
+
+namespace Drupal\views\Plugin\views\display;
+
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpFoundation\JsonResponse;
+
+/**
+ * The plugin that handles a Json response callback.
+ *
+ * @ingroup views_display_plugins
+ *
+ * @Plugin(
+ *   id = "json",
+ *   title = @Translation("Json"),
+ *   help = @Translation("Display the view as a JSON response callback."),
+ *   uses_hook_menu = TRUE,
+ *   admin = @Translation("JSON")
+ * )
+ */
+class Json extends DisplayPluginBase {
+
+  /**
+   * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase::hasPath().
+   */
+  public function hasPath() {
+    return TRUE;
+  }
+
+  /**
+   * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase::usesBreadcrumb().
+   */
+  public function usesBreadcrumb() {
+    return FALSE;
+  }
+
+  /**
+   * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase::defineOptions().
+   */
+  protected function defineOptions() {
+    $options = parent::defineOptions();
+
+    $options['path'] = array('default' => '');
+
+    return $options;
+  }
+
+  /**
+   * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase::executeHookMenu().
+   */
+  public function executeHookMenu($callbacks) {
+    $items = array();
+
+    $bits = explode('/', $this->getOption('path'));
+    $page_arguments = array($this->view->storage->name, $this->display['id']);
+    $this->view->initHandlers();
+    $view_arguments = $this->view->argument;
+
+    // Replace % with %views_arg for menu autoloading and add to the
+    // page arguments so the argument actually comes through.
+    foreach ($bits as $pos => $bit) {
+      if ($bit == '%') {
+        $argument = array_shift($view_arguments);
+        if (!empty($argument->options['specify_validation']) && $argument->options['validate']['type'] != 'none') {
+          $bits[$pos] = '%views_arg';
+        }
+        $page_arguments[] = $pos;
+      }
+    }
+
+    $path = implode('/', $bits);
+
+    $access_plugin = $this->getPlugin('access');
+    if (!isset($access_plugin)) {
+      $access_plugin = views_get_plugin('access', 'none');
+    }
+
+    // Get access callback might return an array of the callback + the dynamic arguments.
+    $access_plugin_callback = $access_plugin->get_access_callback();
+
+    if (is_array($access_plugin_callback)) {
+      $access_arguments = array();
+
+      // Find the plugin arguments.
+      $access_plugin_method = array_shift($access_plugin_callback);
+      $access_plugin_arguments = array_shift($access_plugin_callback);
+      if (!is_array($access_plugin_arguments)) {
+        $access_plugin_arguments = array();
+      }
+
+      $access_arguments[0] = array($access_plugin_method, &$access_plugin_arguments);
+
+      // Move the plugin arguments to the access arguments array.
+      $i = 1;
+      foreach ($access_plugin_arguments as $key => $value) {
+        if (is_int($value)) {
+          $access_arguments[$i] = $value;
+          $access_plugin_arguments[$key] = $i;
+          $i++;
+        }
+      }
+    }
+    else {
+      $access_arguments = array($access_plugin_callback);
+    }
+
+    if ($path) {
+      $items[$path] = array(
+        // default views page entry
+        'page callback' => 'views_page',
+        'page arguments' => $page_arguments,
+        // Default access check (per display)
+        'access callback' => 'views_access',
+        'access arguments' => $access_arguments,
+        // Identify URL embedded arguments and correlate them to a handler
+        'load arguments'  => array($this->view->storage->name, $this->display['id'], '%index'),
+      );
+    }
+
+    return $items;
+  }
+
+  /**
+   * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase::execute().
+   */
+  public function execute() {
+    $this->view->build();
+
+    if (!empty($this->view->build_info['fail'])) {
+      throw new NotFoundHttpException();
+    }
+
+    if (!empty($this->view->build_info['denied'])) {
+      throw new AccessDeniedHttpException();
+    }
+
+    // Execute the view.
+    $this->view->execute();
+
+    return new JsonResponse($this->view->result);
+  }
+
+  /**
+   * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase::preview().
+   */
+  function preview() {
+    $this->view->execute();
+
+    if (!empty($this->view->result)) {
+      $response = new JsonResponse($this->view->result);
+      return $response->getContent();
+    }
+
+    return '';
+  }
+
+  /**
+   * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase::optionsSummary().
+   */
+  public function optionsSummary(&$categories, &$options) {
+    parent::optionsSummary($categories, $options);
+
+    $categories['json'] = array(
+      'title' => t('Json settings'),
+      'column' => 'second',
+      'build' => array(
+        '#weight' => -10,
+      ),
+    );
+
+    $path = strip_tags('/' . $this->getOption('path'));
+    if (empty($path)) {
+      $path = t('None');
+    }
+
+    $options['path'] = array(
+      'category' => 'page',
+      'title' => t('Path'),
+      'value' => views_ui_truncate($path, 24),
+    );
+  }
+
+  /**
+   * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase::buildOptionsForm().
+   */
+  public function buildOptionsForm(&$form, &$form_state) {
+    // It is very important to call the parent function here:
+    parent::buildOptionsForm($form, $form_state);
+
+    switch ($form_state['section']) {
+      case 'path':
+        $form['#title'] .= t('The path or URL of this view');
+        $form['path'] = array(
+          '#type' => 'textfield',
+          '#description' => t('This view will be displayed by visiting this path on your site. You may use "%" in your URL to represent values that will be used for contextual filters: For example, "node/%/json".'),
+          '#default_value' => $this->getOption('path'),
+          '#field_prefix' => '<span dir="ltr">' . url(NULL, array('absolute' => TRUE)),
+          '#field_suffix' => '</span>&lrm;',
+          '#attributes' => array('dir' => 'ltr'),
+        );
+        break;
+      }
+  }
+
+  /**
+   * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase::validateOptionsForm().
+   */
+  public function validateOptionsForm(&$form, &$form_state) {
+    parent::validateOptionsForm($form, $form_state);
+    switch ($form_state['section']) {
+      case 'path':
+        if (strpos($form_state['values']['path'], '$arg') !== FALSE) {
+          form_error($form['path'], t('"$arg" is no longer supported. Use % instead.'));
+        }
+
+        if (strpos($form_state['values']['path'], '%') === 0) {
+          form_error($form['path'], t('"%" may not be used for the first segment of a path.'));
+        }
+
+        // automatically remove '/' and trailing whitespace from path.
+        $form_state['values']['path'] = trim($form_state['values']['path'], '/ ');
+      break;
+    }
+  }
+
+  /**
+   * Overrides Drupal\views\Plugin\views\display\DisplayPluginBase:submitOptionsForm().
+   */
+  public function submitOptionsForm(&$form, &$form_state) {
+    parent::submitOptionsForm($form, $form_state);
+    switch ($form_state['section']) {
+      case 'path':
+        $this->setOption('path', $form_state['values']['path']);
+      break;
+    }
+  }
+
+}
diff --git a/lib/Drupal/views/Tests/Plugin/DisplayJsonTest.php b/lib/Drupal/views/Tests/Plugin/DisplayJsonTest.php
new file mode 100644
index 0000000..3c0491a
--- /dev/null
+++ b/lib/Drupal/views/Tests/Plugin/DisplayJsonTest.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\views\Tests\Plugin\DisplayJsonTest.
+ */
+
+namespace Drupal\views\Tests\Plugin;
+
+use Drupal\views\Tests\Plugin\PluginTestBase;
+
+/**
+ * Tests the JSON display plugin.
+ *
+ * @see Drupal\views\Plugin\display\Json
+ */
+class DisplayJsonTest extends PluginTestBase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Display: JSON plugin',
+      'description' => 'Tests the JSON display plugin.',
+      'group' => 'Views Plugins',
+    );
+  }
+
+  protected function setUp() {
+    parent::setUp();
+
+    for ($i = 1; $i <= 10; $i++) {
+      $this->drupalCreateNode();
+    }
+
+    $this->enableViewsTestModule();
+  }
+
+  /**
+   * Checks the behavior of JSON callback paths.
+   */
+  public function testJsonResponses() {
+    $view = views_get_view('test_json_display');
+    $view->initDisplay();
+    $this->executeView($view);
+
+    // Test the JSON callback.
+    $actual_json = $this->drupalGet('test/json');
+    $this->assertResponse(200);
+    // Test the http content-type.
+    $headers = $this->drupalGetHeaders();
+    $this->assertEqual($headers['content-type'], 'application/json', 'The header content-type is correct.');
+
+    $expected_json = json_encode($view->result);
+    $this->assertIdentical($actual_json, $expected_json, 'The expected JSON output was found.');
+
+    // Test a 403 callback.
+    $this->drupalGet('test/json/denied');
+    $this->assertResponse(403);
+  }
+
+}
diff --git a/tests/views_test_config/config/views.view.test_json_display.yml b/tests/views_test_config/config/views.view.test_json_display.yml
new file mode 100644
index 0000000..1f086f6
--- /dev/null
+++ b/tests/views_test_config/config/views.view.test_json_display.yml
@@ -0,0 +1,74 @@
+base_table: views_test_data
+name: test_json_display
+description: ''
+tag: ''
+human_name: 'Test JSON display'
+core: 8.x
+api_version: '3.0'
+display:
+  default:
+    display_plugin: default
+    id: default
+    display_title: Master
+    position: ''
+    display_options:
+      access:
+        type: perm
+        options:
+          perm: 'view published content'
+      cache:
+        type: none
+      query:
+        type: views_query
+      exposed_form:
+        type: basic
+      pager:
+        type: full
+        options:
+          items_per_page: '10'
+      style:
+        type: default
+      row:
+        type: fields
+      fields:
+        name:
+          id: name
+          table: views_test_data
+          field: name
+          label: ''
+      sorts:
+        created:
+          id: created
+          table: views_test_data
+          field: created
+          order: DESC
+      title: 'Test JSON'
+      arguments: {  }
+  json_1:
+    display_plugin: json
+    id: json_1
+    display_title: Json
+    position: ''
+    display_options:
+      defaults:
+        access: false
+      path: test/json
+      access:
+        type: none
+  json_2:
+    display_plugin: json
+    id: json_2
+    display_title: 'Json - access denied'
+    position: ''
+    display_options:
+      defaults:
+        access: false
+      path: test/json/denied
+      access:
+        type: perm
+        options:
+          perm: 'administer views'
+base_field: id
+disabled: '0'
+module: views
+langcode: und
