diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php
index 06bb9f9..bc775f2 100644
--- a/src/wp-includes/rest-api.php
+++ b/src/wp-includes/rest-api.php
@@ -1051,6 +1051,25 @@ function rest_validate_value_from_schema( $value, $args, $param = '' ) {
 			}
 		}
 	}
+
+	if ( 'object' === $args['type'] ) {
+		if ( ! is_array( $value ) ) {
+			/* translators: 1: parameter, 2: type name */
+			return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ) );
+		}
+
+		foreach ( $value as $property => $v ) {
+			if ( ! isset( $args['properties'][ $property ] ) ) {
+				continue;
+			}
+			$is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
+
+			if ( is_wp_error( $is_valid ) ) {
+				return $is_valid;
+			}
+		}
+	}
+
 	if ( ! empty( $args['enum'] ) ) {
 		if ( ! in_array( $value, $args['enum'], true ) ) {
 			/* translators: 1: parameter, 2: list of valid values */
@@ -1170,6 +1189,23 @@ function rest_sanitize_value_from_schema( $value, $args ) {
 		$value = array_values( $value );
 		return $value;
 	}
+
+	if ( 'object' === $args['type'] ) {
+		if ( ! is_array( $value ) ) {
+			return array();
+		}
+
+		foreach ( $value as $property => $v ) {
+			if ( ! isset( $args['properties'][ $property ] ) ) {
+				unset( $value[ $property ] );
+				continue;
+			}
+			$value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ] );
+		}
+
+		return $value;
+	}
+
 	if ( 'integer' === $args['type'] ) {
 		return (int) $value;
 	}
diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php
index 76802fe..ed10181 100644
--- a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php
+++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php
@@ -391,6 +391,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
 			'context'     => array( 'view', 'edit', 'embed' ),
 			'arg_options' => array(
 				'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database()
+				'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database()
 			),
 			'properties'  => array(
 				'raw' => array(
@@ -413,6 +414,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
 			'context'     => array( 'view', 'edit' ),
 			'arg_options' => array(
 				'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database()
+				'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database()
 			),
 			'properties'  => array(
 				'raw' => array(
diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php
index 810a3fb..415ba8e 100644
--- a/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php
+++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php
@@ -1200,6 +1200,7 @@ class WP_REST_Comments_Controller extends WP_REST_Controller {
 					'context'         => array( 'view', 'edit', 'embed' ),
 					'arg_options'     => array(
 						'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database()
+						'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database()
 					),
 					'properties'      => array(
 						'raw'         => array(
diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
index 018b66e..80027cf 100644
--- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
+++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
@@ -1846,6 +1846,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
 						'context'     => array( 'view', 'edit', 'embed' ),
 						'arg_options' => array(
 							'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database()
+							'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database()
 						),
 						'properties'  => array(
 							'raw' => array(
@@ -1870,6 +1871,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
 						'context'     => array( 'view', 'edit' ),
 						'arg_options' => array(
 							'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database()
+							'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database()
 						),
 						'properties'  => array(
 							'raw' => array(
@@ -1908,6 +1910,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
 						'context'     => array( 'view', 'edit', 'embed' ),
 						'arg_options' => array(
 							'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database()
+							'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database()
 						),
 						'properties'  => array(
 							'raw' => array(
diff --git a/tests/phpunit/tests/rest-api/rest-schema-sanitization.php b/tests/phpunit/tests/rest-api/rest-schema-sanitization.php
index e1ae29d..a365d9c 100644
--- a/tests/phpunit/tests/rest-api/rest-schema-sanitization.php
+++ b/tests/phpunit/tests/rest-api/rest-schema-sanitization.php
@@ -146,6 +146,72 @@ class WP_Test_REST_Schema_Sanitization extends WP_UnitTestCase {
 		$this->assertEquals( array( '1', '2' ), rest_sanitize_value_from_schema( array( 'first' => '1', 'second' => '2' ), $schema ) );
 	}
 
+	public function test_type_object() {
+		$schema = array(
+			'type'       => 'object',
+			'properties' => array(
+				'a' => array(
+					'type' => 'number'
+				),
+			),
+		);
+		$this->assertEquals( array( 'a' => 1 ), rest_sanitize_value_from_schema( array( 'a' => 1 ), $schema ) );
+		$this->assertEquals( array( 'a' => 1 ), rest_sanitize_value_from_schema( array( 'a' => '1' ), $schema ) );
+	}
+
+	public function test_type_object_nested() {
+		$schema = array(
+			'type' => 'object',
+			'properties' => array(
+				'a' => array(
+					'type'  => 'object',
+					'properties' => array(
+						'b' => array( 'type' => 'number' ),
+						'c' => array( 'type' => 'number' ),
+					)
+				)
+			),
+		);
+
+		$this->assertEquals(
+			array(
+				'a' => array(
+					'b' => 1,
+					'c' => 3,
+				),
+			),
+			rest_sanitize_value_from_schema(
+				array(
+					'a' => array(
+						'b' => '1',
+						'c' => '3',
+					),
+				),
+				$schema
+			)
+		);
+		$this->assertEquals(
+			array(
+				'a' => array(
+					'b' => 1,
+					'c' => 3,
+				),
+			),
+			rest_sanitize_value_from_schema(
+				array(
+					'a' => array(
+						'b' => '1',
+						'c' => '3',
+						'd' => '1',
+					),
+					'b' => 1,
+				),
+				$schema
+			)
+		);
+		$this->assertEquals( array( 'a' => array() ), rest_sanitize_value_from_schema( array( 'a' => null ), $schema ) );
+	}
+
 	public function test_type_unknown() {
 		$schema = array(
 			'type' => 'lalala',
diff --git a/tests/phpunit/tests/rest-api/rest-schema-validation.php b/tests/phpunit/tests/rest-api/rest-schema-validation.php
index 82f58e5..c47ed10 100644
--- a/tests/phpunit/tests/rest-api/rest-schema-validation.php
+++ b/tests/phpunit/tests/rest-api/rest-schema-validation.php
@@ -181,6 +181,47 @@ class WP_Test_REST_Schema_Validation extends WP_UnitTestCase {
 		$this->assertWPError( rest_validate_value_from_schema( array( 'first' => '1', 'second' => '2' ), $schema ) );
 	}
 
+	public function test_type_object() {
+		$schema = array(
+			'type'       => 'object',
+			'properties' => array(
+				'a' => array(
+					'type' => 'number'
+				),
+			),
+		);
+		$this->assertTrue( rest_validate_value_from_schema( array( 'a' => 1 ), $schema ) );
+		$this->assertWPError( rest_validate_value_from_schema( array( 'a' => 'invalid' ), $schema ) );
+	}
+
+	public function test_type_object_nested() {
+		$schema = array(
+			'type' => 'object',
+			'properties' => array(
+				'a' => array(
+					'type'  => 'object',
+					'properties' => array(
+						'b' => array( 'type' => 'number' ),
+						'c' => array( 'type' => 'number' ),
+					)
+				)
+			),
+		);
+		$this->assertTrue(
+			rest_validate_value_from_schema(
+				array(
+					'a' => array(
+						'b' => '1',
+						'c' => 3,
+					),
+				),
+				$schema
+			)
+		);
+		$this->assertWPError( rest_validate_value_from_schema( array( 'a' => array( 'b' => 1, 'c' => 'invalid' ) ), $schema ) );
+		$this->assertWPError( rest_validate_value_from_schema( array( 'a' => 1 ), $schema ) );
+	}
+
 	public function test_type_unknown() {
 		$schema = array(
 			'type'  => 'lalala',
