diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index be000dc..e3a3122 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -803,7 +803,7 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) {
       $form['#attributes']['data-drupal-selector'] = Html::getId($form_id);
     }
 
-    $form += $this->elementInfo->getInfo('form');
+    $form = NestedArray::mergeDeepArray([$this->elementInfo->getInfo('form'), $form], TRUE);
     $form += ['#tree' => FALSE, '#parents' => []];
     $form['#validate'][] = '::validateForm';
     $form['#submit'][] = '::submitForm';
@@ -908,7 +908,7 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
     // Use element defaults.
     if (isset($element['#type']) && empty($element['#defaults_loaded']) && ($info = $this->elementInfo->getInfo($element['#type']))) {
       // Overlay $info onto $element, retaining preexisting keys in $element.
-      $element += $info;
+      $element = NestedArray::mergeDeepArray([$info, $element], TRUE);
       $element['#defaults_loaded'] = TRUE;
     }
     // Assign basic defaults common for all form elements.
@@ -1011,7 +1011,7 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
       // Prior to checking properties of child elements, their default
       // properties need to be loaded.
       if (isset($element[$key]['#type']) && empty($element[$key]['#defaults_loaded']) && ($info = $this->elementInfo->getInfo($element[$key]['#type']))) {
-        $element[$key] += $info;
+        $element[$key] = NestedArray::mergeDeepArray([$info, $element[$key]], TRUE);
         $element[$key]['#defaults_loaded'] = TRUE;
       }
 
diff --git a/core/lib/Drupal/Core/Render/MainContent/AjaxRenderer.php b/core/lib/Drupal/Core/Render/MainContent/AjaxRenderer.php
index 2b2a2a5..ed4df42 100644
--- a/core/lib/Drupal/Core/Render/MainContent/AjaxRenderer.php
+++ b/core/lib/Drupal/Core/Render/MainContent/AjaxRenderer.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Core\Render\MainContent;
 
+use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\AlertCommand;
 use Drupal\Core\Ajax\InsertCommand;
@@ -48,7 +49,7 @@ public function renderResponse(array $main_content, Request $request, RouteMatch
     if (isset($main_content['#type']) && ($main_content['#type'] == 'ajax')) {
       // Complex Ajax callbacks can return a result that contains an error
       // message or a specific set of commands to send to the browser.
-      $main_content += $this->elementInfoManager->getInfo('ajax');
+      $main_content = NestedArray::mergeDeep($this->elementInfoManager->getInfo('ajax'), $main_content);
       $error = $main_content['#error'];
       if (!empty($error)) {
         // Fall back to some default message otherwise use the specific one.
diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 287ffbb..aa9755d 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -4,6 +4,7 @@
 
 use Drupal\Component\Render\MarkupInterface;
 use Drupal\Component\Utility\Html;
+use Drupal\Component\Utility\NestedArray;
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Access\AccessResultInterface;
 use Drupal\Core\Cache\Cache;
@@ -295,7 +296,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
     // If the default values for this element have not been loaded yet, populate
     // them.
     if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {
-      $elements += $this->elementInfo->getInfo($elements['#type']);
+      $elements = NestedArray::mergeDeep($this->elementInfo->getInfo($elements['#type']), $elements);
     }
 
     // First validate the usage of #lazy_builder; both of the next if-statements
