diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-controller.php
index 78c3567..2fb5221 100644
--- a/src/wp-includes/rest-api/endpoints/class-wp-rest-controller.php
+++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-controller.php
@@ -545,7 +545,7 @@ abstract class WP_REST_Controller {
 				$endpoint_args[ $field_id ]['required'] = true;
 			}
 
-			foreach ( array( 'type', 'format', 'enum', 'items' ) as $schema_prop ) {
+			foreach ( array( 'type', 'format', 'enum', 'items', 'properties' ) as $schema_prop ) {
 				if ( isset( $params[ $schema_prop ] ) ) {
 					$endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
 				}
diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php
index 1618e35..21e467f 100644
--- a/src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php
+++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php
@@ -119,23 +119,13 @@ class WP_REST_Settings_Controller extends WP_REST_Controller {
 	 * @return mixed The prepared value.
 	 */
 	protected function prepare_value( $value, $schema ) {
-		// If the value is not a scalar, it's not possible to cast it to anything.
-		if ( ! is_scalar( $value ) ) {
+		// If the value is not valid by the schema, set the value to null. Null
+		// values are specifcally non-destructive so this will not cause overwriting
+		// the current invalid value to null.
+		if ( is_wp_error( rest_validate_value_from_schema( $value, $schema ) ) ) {
 			return null;
 		}
-
-		switch ( $schema['type'] ) {
-			case 'string':
-				return (string) $value;
-			case 'integer':
-				return (int) $value;
-			case 'number':
-				return (float) $value;
-			case 'boolean':
-				return (bool) $value;
-			default:
-				return null;
-		}
+		return rest_sanitize_value_from_schema( $value, $schema );
 	}
 
 	/**
@@ -148,6 +138,7 @@ class WP_REST_Settings_Controller extends WP_REST_Controller {
 	 */
 	public function update_item( $request ) {
 		$options = $this->get_registered_options();
+
 		$params  = $request->get_params();
 
 		foreach ( $options as $name => $args ) {
@@ -187,12 +178,12 @@ class WP_REST_Settings_Controller extends WP_REST_Controller {
 				 *
 				 * To protect clients from accidentally including the null
 				 * values from a response object in a request, we do not allow
-				 * options with non-scalar values to be updated to null.
+				 * options with values that don't pass validation to be updated to null.
 				 * Without this added protection a client could mistakenly
-				 * delete all options that have non-scalar values from the
+				 * delete all options that have invalid values from the
 				 * database.
 				 */
-				if ( ! is_scalar( get_option( $args['option_name'], false ) ) ) {
+				if ( is_wp_error( rest_validate_value_from_schema( get_option( $args['option_name'], false ), $args['schema'] ) ) ) {
 					return new WP_Error(
 						'rest_invalid_stored_value', sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ), array( 'status' => 500 )
 					);
@@ -253,7 +244,7 @@ class WP_REST_Settings_Controller extends WP_REST_Controller {
 			 * Whitelist the supported types for settings, as we don't want invalid types
 			 * to be updated with arbitrary values that we can't do decent sanitizing for.
 			 */
-			if ( ! in_array( $rest_args['schema']['type'], array( 'number', 'integer', 'string', 'boolean' ), true ) ) {
+			if ( ! in_array( $rest_args['schema']['type'], array( 'number', 'integer', 'string', 'boolean', 'array', 'object' ), true ) ) {
 				continue;
 			}
 
diff --git a/tests/phpunit/tests/rest-api/rest-settings-controller.php b/tests/phpunit/tests/rest-api/rest-settings-controller.php
index 853a013..b4d7c68 100644
--- a/tests/phpunit/tests/rest-api/rest-settings-controller.php
+++ b/tests/phpunit/tests/rest-api/rest-settings-controller.php
@@ -127,6 +127,93 @@ class WP_Test_REST_Settings_Controller extends WP_Test_REST_Controller_Testcase
 		unregister_setting( 'somegroup', 'mycustomsetting' );
 	}
 
+	public function test_get_item_with_custom_array_setting() {
+		wp_set_current_user( self::$administrator );
+
+		register_setting( 'somegroup', 'mycustomsetting', array(
+			'show_in_rest' => array(
+				'schema' => array(
+					'type'    => 'array',
+					'items'   => array(
+						'type' => 'integer',
+					),
+				),
+			),
+			'type'         => 'array',
+		) );
+
+		// Array is cast to correct types.
+		update_option( 'mycustomsetting', array( '1', '2' ) );
+		$request = new WP_REST_Request( 'GET', '/wp/v2/settings' );
+		$response = $this->server->dispatch( $request );
+		$data = $response->get_data();
+		$this->assertEquals( array( 1, 2 ), $data['mycustomsetting'] );
+
+		// Empty array works as expected.
+		update_option( 'mycustomsetting', array() );
+		$request = new WP_REST_Request( 'GET', '/wp/v2/settings' );
+		$response = $this->server->dispatch( $request );
+		$data = $response->get_data();
+		$this->assertEquals( array(), $data['mycustomsetting'] );
+
+		// Invalid value
+		update_option( 'mycustomsetting', array( array( 1 ) ) );
+		$request = new WP_REST_Request( 'GET', '/wp/v2/settings' );
+		$response = $this->server->dispatch( $request );
+		$data = $response->get_data();
+		$this->assertEquals( null, $data['mycustomsetting'] );
+
+		// No option value
+		delete_option( 'mycustomsetting' );
+		$request = new WP_REST_Request( 'GET', '/wp/v2/settings' );
+		$response = $this->server->dispatch( $request );
+		$data = $response->get_data();
+		$this->assertEquals( null, $data['mycustomsetting'] );
+
+		unregister_setting( 'somegroup', 'mycustomsetting' );
+	}
+
+	public function test_get_item_with_custom_object_setting() {
+		wp_set_current_user( self::$administrator );
+
+		register_setting( 'somegroup', 'mycustomsetting', array(
+			'show_in_rest' => array(
+				'schema' => array(
+					'type'    => 'object',
+					'properties' => array(
+						'a' => array(
+							'type' => 'integer',
+						),
+					),
+				),
+			),
+			'type'         => 'object',
+		) );
+
+		// Object is cast to correct types.
+		update_option( 'mycustomsetting', array( 'a' => '1' ) );
+		$request = new WP_REST_Request( 'GET', '/wp/v2/settings' );
+		$response = $this->server->dispatch( $request );
+		$data = $response->get_data();
+		$this->assertEquals( array( 'a' => 1 ), $data['mycustomsetting'] );
+
+		// Empty array works as expected.
+		update_option( 'mycustomsetting', array() );
+		$request = new WP_REST_Request( 'GET', '/wp/v2/settings' );
+		$response = $this->server->dispatch( $request );
+		$data = $response->get_data();
+		$this->assertEquals( array(), $data['mycustomsetting'] );
+
+		// Invalid value
+		update_option( 'mycustomsetting', array( 'a' => 1, 'b' => 2 ) );
+		$request = new WP_REST_Request( 'GET', '/wp/v2/settings' );
+		$response = $this->server->dispatch( $request );
+		$data = $response->get_data();
+		$this->assertEquals( array( 'a' => 1 ), $data['mycustomsetting'] );
+
+		unregister_setting( 'somegroup', 'mycustomsetting' );
+	}
+
 	public function get_setting_custom_callback( $result, $name, $args ) {
 		switch ( $name ) {
 			case 'mycustomsetting1':
@@ -215,6 +302,7 @@ class WP_Test_REST_Settings_Controller extends WP_Test_REST_Controller_Testcase
 		$response = $this->server->dispatch( $request );
 		$data = $response->get_data();
 		$this->assertEquals( null, $data['mycustomsettinginrest'] );
+		unregister_setting( 'somegroup', 'mycustomsetting' );
 	}
 
 
@@ -242,6 +330,91 @@ class WP_Test_REST_Settings_Controller extends WP_Test_REST_Controller_Testcase
 		return false;
 	}
 
+	public function test_update_item_with_array() {
+		register_setting( 'somegroup', 'mycustomsetting', array(
+			'show_in_rest' => array(
+				'schema' => array(
+					'type'  => 'array',
+					'items' => array(
+						'type' => 'integer',
+					),
+				),
+			),
+			'type'         => 'array',
+		) );
+
+		// We have to re-register the route, as the args changes based off registered settings.
+		$this->server->override_by_default = true;
+		$this->endpoint->register_routes();
+		wp_set_current_user( self::$administrator );
+
+		$request = new WP_REST_Request( 'PUT', '/wp/v2/settings' );
+		$request->set_param( 'mycustomsetting', array( '1', '2' ) );
+		$response = $this->server->dispatch( $request );
+		$data = $response->get_data();
+		$this->assertEquals( array( 1, 2 ), $data['mycustomsetting'] );
+		$this->assertEquals( array( 1, 2 ), get_option( 'mycustomsetting' ) );
+
+		// Setting an empty array.
+		$request = new WP_REST_Request( 'PUT', '/wp/v2/settings' );
+		$request->set_param( 'mycustomsetting', array() );
+		$response = $this->server->dispatch( $request );
+		$data = $response->get_data();
+		$this->assertEquals( array(), $data['mycustomsetting'] );
+		$this->assertEquals( array(), get_option( 'mycustomsetting' ) );
+
+		// Setting an invalid array.
+		$request = new WP_REST_Request( 'PUT', '/wp/v2/settings' );
+		$request->set_param( 'mycustomsetting', array( 'invalid' ) );
+		$response = $this->server->dispatch( $request );
+
+		$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
+		unregister_setting( 'somegroup', 'mycustomsetting' );
+	}
+
+	public function test_update_item_with_object() {
+		register_setting( 'somegroup', 'mycustomsetting', array(
+			'show_in_rest' => array(
+				'schema' => array(
+					'type'       => 'object',
+					'properties' => array(
+						'a' => array(
+							'type' => 'integer',
+						),
+					),
+				),
+			),
+			'type'         => 'object',
+		) );
+
+		// We have to re-register the route, as the args changes based off registered settings.
+		$this->server->override_by_default = true;
+		$this->endpoint->register_routes();
+		wp_set_current_user( self::$administrator );
+
+		$request = new WP_REST_Request( 'PUT', '/wp/v2/settings' );
+		$request->set_param( 'mycustomsetting', array( 'a' => 1 ) );
+		$response = $this->server->dispatch( $request );
+		$data = $response->get_data();
+		$this->assertEquals( array( 'a' => 1 ), $data['mycustomsetting'] );
+		$this->assertEquals( array( 'a' => 1 ), get_option( 'mycustomsetting' ) );
+
+		// Setting an empty object.
+		$request = new WP_REST_Request( 'PUT', '/wp/v2/settings' );
+		$request->set_param( 'mycustomsetting', array() );
+		$response = $this->server->dispatch( $request );
+		$data = $response->get_data();
+		$this->assertEquals( array(), $data['mycustomsetting'] );
+		$this->assertEquals( array(), get_option( 'mycustomsetting' ) );
+
+		// Setting an invalid object.
+		$request = new WP_REST_Request( 'PUT', '/wp/v2/settings' );
+		$request->set_param( 'mycustomsetting', array( 'a' => 'invalid' ) );
+		$response = $this->server->dispatch( $request );
+		$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
+		unregister_setting( 'somegroup', 'mycustomsetting' );
+	}
+
 	public function test_update_item_with_filter() {
 		wp_set_current_user( self::$administrator );
 
