diff --git a/core/includes/ajax.inc b/core/includes/ajax.inc
index c67e981..9e850bb 100644
--- a/core/includes/ajax.inc
+++ b/core/includes/ajax.inc
@@ -290,7 +290,7 @@ function ajax_render($commands = array()) {
   $scripts = drupal_add_js();
   if (!empty($scripts['settings'])) {
     $settings = $scripts['settings'];
-    array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $settings['data']), TRUE));
+    array_unshift($commands, ajax_command_settings(drupal_array_merge_deep_array($settings['data']), TRUE));
   }
 
   // Allow modules to alter any Ajax response.
diff --git a/core/includes/module.inc b/core/includes/module.inc
index abe03dd..3773f1b 100644
--- a/core/includes/module.inc
+++ b/core/includes/module.inc
@@ -919,7 +919,7 @@ function module_hook_info() {
         if (function_exists($function)) {
           $result = $function();
           if (isset($result) && is_array($result)) {
-            $hook_info = array_merge_recursive($hook_info, $result);
+            $hook_info = drupal_array_merge_deep($hook_info, $result);
           }
         }
       }
@@ -1000,7 +1000,7 @@ function module_invoke_all($hook) {
     if (function_exists($function)) {
       $result = call_user_func_array($function, $args);
       if (isset($result) && is_array($result)) {
-        $return = array_merge_recursive($return, $result);
+        $return = drupal_array_merge_deep($return, $result);
       }
       elseif (isset($result)) {
         $return[] = $result;
diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponse.php b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
index 492665e..516325b 100644
--- a/core/lib/Drupal/Core/Ajax/AjaxResponse.php
+++ b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
@@ -9,6 +9,7 @@
 
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
+use Drupal\Component\Utility\NestedArray;
 
 /**
  * JSON response object for AJAX requests.
@@ -134,7 +135,7 @@ protected function ajaxRender(Request $request) {
     $scripts = drupal_add_js();
     if (!empty($scripts['settings'])) {
       $settings = $scripts['settings'];
-      $this->addCommand(new SettingsCommand(call_user_func_array('array_merge_recursive', $settings['data']), TRUE), TRUE);
+      $this->addCommand(new SettingsCommand(NestedArray::mergeDeepArray($settings['data']), TRUE), TRUE);
     }
 
     $commands = $this->commands;
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
index bf515de..b92322b 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Entity\Query\QueryInterface;
 use Exception;
 use Drupal\Component\Uuid\Uuid;
+use Drupal\Component\Utility\NestedArray;
 
 
 /**
@@ -664,9 +665,9 @@ public function getFieldDefinitions(array $constraints) {
 
       // Invoke hooks.
       $result = module_invoke_all($this->entityType . '_property_info');
-      $this->entityFieldInfo = array_merge_recursive($this->entityFieldInfo, $result);
+      $this->entityFieldInfo = NestedArray::mergeDeep($this->entityFieldInfo, $result);
       $result = module_invoke_all('entity_field_info', $this->entityType);
-      $this->entityFieldInfo = array_merge_recursive($this->entityFieldInfo, $result);
+      $this->entityFieldInfo = NestedArray::mergeDeep($this->entityFieldInfo, $result);
 
       $hooks = array('entity_field_info', $this->entityType . '_property_info');
       drupal_alter($hooks, $this->entityFieldInfo, $this->entityType);
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index fd2b269..21026c8 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -785,7 +785,7 @@ function file_ajax_upload() {
 
   $output = theme('status_messages') . drupal_render($form);
   $js = drupal_add_js();
-  $settings = call_user_func_array('array_merge_recursive', $js['settings']['data']);
+  $settings = drupal_array_merge_deep_array($js['settings']['data']);
 
   $response = new AjaxResponse();
   return $response->addCommand(new ReplaceCommand(NULL, $output, $settings));
diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php
index 0b892eb..7dff3b3 100644
--- a/core/modules/node/lib/Drupal/node/NodeFormController.php
+++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
@@ -91,7 +91,7 @@ public function form(array $form, array &$form_state, EntityInterface $node) {
     //   handlers to the form buttons below. Remove hook_form() entirely.
     $function = node_hook($node->type, 'form');
     if ($function && ($extra = $function($node, $form_state))) {
-      $form = array_merge_recursive($form, $extra);
+      $form = drupal_array_merge_deep($form, $extra);
     }
     // If the node type has a title, and the node type form defined no special
     // weight for it, we default to a weight of -5 for consistency.
diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module
index 2e04773..553cf95 100644
--- a/core/modules/rdf/rdf.module
+++ b/core/modules/rdf/rdf.module
@@ -703,7 +703,7 @@ function rdf_preprocess_username(&$variables) {
   // (see http://www.w3.org/TR/rdfa-syntax/#rdfa-attributes).
   // Therefore, merge rather than override so as not to clobber values set by
   // earlier preprocess functions.
-  $variables['attributes'] = array_merge_recursive($variables['attributes'], $attributes);
+  $variables['attributes'] = drupal_array_merge_deep($variables['attributes'], $attributes);
 }
 
 /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Ajax/CommandsTest.php b/core/modules/system/lib/Drupal/system/Tests/Ajax/CommandsTest.php
index 5a455b7..60ef056 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Ajax/CommandsTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Ajax/CommandsTest.php
@@ -158,6 +158,14 @@ function testAjaxCommands() {
     );
     $this->assertCommand($commands, $expected, "'settings' AJAX command issued with correct data");
 
+     // Test that the settings command merges settings properly.
+     $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'settings' command with setting merging")));
+     $expected = array(
+       'command' => 'settings',
+       'settings' => array('ajax_forms_test' => array('foo' => 9001)),
+     );
+     $this->assertCommand($commands, $expected, "'settings' AJAX command with setting merging");
+
     // Tests the 'add_css' command.
     $commands = $this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'add_css' command")));
     $expected = array(
diff --git a/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module b/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module
index 96288f9..4358d2a 100644
--- a/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module
+++ b/core/modules/system/tests/modules/ajax_forms_test/ajax_forms_test.module
@@ -245,6 +245,18 @@ function ajax_forms_test_ajax_commands_form($form, &$form_state) {
     ),
   );
 
+  // Tests the 'settings' command with a callback which sets the same
+  // setting multiple times. This is used to check that settings are
+  // merged properly (e.g., array_merge_recursive() merges settings
+  // incorrectly, #1356170).
+  $form['settings_command_with_merging_example'] = array(
+    '#type' => 'submit',
+    '#value' => t("AJAX 'settings' command with setting merging"),
+    '#ajax' => array(
+      'callback' => 'ajax_forms_test_advanced_commands_settings_with_merging_callback',
+    ),
+  );
+
   $form['submit'] = array(
     '#type' => 'submit',
     '#value' => t('Submit'),
diff --git a/core/modules/translation/translation.pages.inc b/core/modules/translation/translation.pages.inc
index 7a9880c..94c6a4c 100644
--- a/core/modules/translation/translation.pages.inc
+++ b/core/modules/translation/translation.pages.inc
@@ -71,7 +71,7 @@ function translation_node_overview(Node $node) {
           $options['add'] = array(
             'title' => t('add translation'),
           ) + $links->links[$langcode];
-          $options['add'] = array_merge_recursive($options['add'], $query);
+          $options['add'] = drupal_array_merge_deep($options['add'], $query);
         }
       }
       $status = t('Not translated');
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 7dabb6d..238155d 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -941,6 +941,8 @@ function template_process_username(&$variables) {
     // to use the former unless they want to add attributes on the link only.
     // If a link is being rendered, these need to be merged. Some attributes are
     // themselves arrays, so the merging needs to be recursive.
+    // This purposefully does not use drupal_array_merge_deep() for performance
+    // reasons, since it is potentially called very often.
     $variables['link_options']['attributes'] = array_merge_recursive($variables['link_attributes'], $variables['attributes']);
   }
 }
