Index: src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php
===================================================================
--- src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php	(revision 39184)
+++ src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php	(working copy)
@@ -84,7 +84,7 @@
 			$response[ $name ] = $value;
 		}
 
-		return (object) $response;
+		return $response;
 	}
 
 	/**
@@ -133,10 +133,24 @@
 			 */
 			if ( is_null( $request[ $name ] ) ) {
 				$result = $this->delete_meta_value( $object_id, $name );
-			} elseif ( $args['single'] ) {
-				$result = $this->update_meta_value( $object_id, $name, $request[ $name ] );
+				if ( is_wp_error( $result ) ) {
+					return $result;
+				}
+				continue;
+			}
+
+			$is_valid = rest_validate_value_from_schema( $request[ $name ], $args['schema'], 'meta.' . $name );
+			if ( is_wp_error( $is_valid ) ) {
+				$is_valid->add_data( array( 'status' => 400 ) );
+				return $is_valid;
+			}
+
+			$value = rest_sanitize_value_from_schema( $request[ $name ], $args['schema'] );
+
+			if ( $args['single'] ) {
+				$result = $this->update_meta_value( $object_id, $name, $value );
 			} else {
-				$result = $this->update_multi_meta_value( $object_id, $name, $request[ $name ] );
+				$result = $this->update_multi_meta_value( $object_id, $name, $value );
 			}
 
 			if ( is_wp_error( $result ) ) {
@@ -319,6 +333,7 @@
 			$default_args = array(
 				'name'             => $name,
 				'single'           => $args['single'],
+				'type'             => ! empty( $args['type'] ) ? $args['type'] : null,
 				'schema'           => array(),
 				'prepare_callback' => array( $this, 'prepare_value' ),
 			);
@@ -332,6 +347,13 @@
 			$rest_args = array_merge( $default_args, $rest_args );
 			$rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] );
 
+			$type = ! empty( $rest_args['type'] ) ? $rest_args['type'] : null;
+			$type = ! empty( $rest_args['schema']['type'] ) ? $rest_args['schema']['type'] : $type;
+
+			if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number' ) ) ) {
+				continue;
+			}
+
 			if ( empty( $rest_args['schema']['type'] ) ) {
 				// Skip over meta fields that don't have a defined type.
 				if ( empty( $args['type'] ) ) {
Index: src/wp-includes/rest-api.php
===================================================================
--- src/wp-includes/rest-api.php	(revision 39184)
+++ src/wp-includes/rest-api.php	(working copy)
@@ -998,6 +998,9 @@
 		if ( ! is_array( $value ) ) {
 			$value = preg_split( '/[\s,]+/', $value );
 		}
+		if ( ! wp_is_numeric_array( $value ) ) {
+			return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, 'array' ) );
+		}
 		foreach ( $value as $index => $v ) {
 			$is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
 			if ( is_wp_error( $is_valid ) ) {
@@ -1107,6 +1110,9 @@
 		foreach ( $value as $index => $v ) {
 			$value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'] );
 		}
+		// Normalize to numeric array so nothing unexpected
+		// is in the keys.
+		$value = array_values( $value );
 		return $value;
 	}
 	if ( 'integer' === $args['type'] ) {
@@ -1140,5 +1146,9 @@
 		}
 	}
 
+	if ( 'string' === $args['type'] ) {
+		return strval( $value );
+	}
+
 	return $value;
 }
Index: tests/phpunit/tests/rest-api/rest-post-meta-fields.php
===================================================================
--- tests/phpunit/tests/rest-api/rest-post-meta-fields.php	(revision 39184)
+++ tests/phpunit/tests/rest-api/rest-post-meta-fields.php	(working copy)
@@ -26,24 +26,29 @@
 		register_meta( 'post', 'test_single', array(
 			'show_in_rest' => true,
 			'single' => true,
+			'type' => 'string',
 		));
 		register_meta( 'post', 'test_multi', array(
 			'show_in_rest' => true,
 			'single' => false,
+			'type' => 'string',
 		));
 		register_meta( 'post', 'test_bad_auth', array(
 			'show_in_rest' => true,
 			'single' => true,
 			'auth_callback' => '__return_false',
+			'type' => 'string',
 		));
 		register_meta( 'post', 'test_bad_auth_multi', array(
 			'show_in_rest' => true,
 			'single' => false,
 			'auth_callback' => '__return_false',
+			'type' => 'string',
 		));
 		register_meta( 'post', 'test_no_rest', array() );
 		register_meta( 'post', 'test_rest_disabled', array(
 			'show_in_rest' => false,
+			'type' => 'string',
 		));
 		register_meta( 'post', 'test_custom_schema', array(
 			'single' => true,
@@ -56,7 +61,12 @@
 		));
 		register_meta( 'post', 'test_invalid_type', array(
 			'single' => true,
-			'type' => false,
+			'type' => 'lalala',
+			'show_in_rest' => true,
+		));
+		register_meta( 'post', 'test_no_type', array(
+			'single' => true,
+			'type' => null,
 			'show_in_rest' => true,
 		));
 
@@ -240,6 +250,8 @@
 		$this->assertEquals( 'test_value', $meta['test_single'] );
 	}
 
+
+
 	/**
 	 * @depends test_get_value
 	 */
@@ -341,6 +353,24 @@
 		$wpdb->show_errors = true;
 	}
 
+	public function test_set_value_invalid_type() {
+		$values = get_post_meta( self::$post_id, 'test_invalid_type', false );
+		$this->assertEmpty( $values );
+
+		$this->grant_write_permission();
+
+		$data = array(
+			'meta' => array(
+				'test_invalid_type' => 'test_value',
+			),
+		);
+		$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
+		$request->set_body_params( $data );
+
+		$response = $this->server->dispatch( $request );
+		$this->assertEmpty( get_post_meta( self::$post_id, 'test_invalid_type', false ) );
+	}
+
 	public function test_set_value_multiple() {
 		// Ensure no data exists currently.
 		$values = get_post_meta( self::$post_id, 'test_multi', false );
@@ -435,6 +465,92 @@
 		$this->assertEmpty( $meta );
 	}
 
+	public function test_set_value_invalid_value() {
+		register_meta( 'post', 'my_meta_key', array(
+			'show_in_rest' => true,
+			'single' => true,
+			'type' => 'string',
+		));
+
+		$this->grant_write_permission();
+
+		$data = array(
+			'meta' => array(
+				'my_meta_key' => array( 'c', 'n' ),
+			),
+		);
+		$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
+		$request->set_body_params( $data );
+
+		$response = $this->server->dispatch( $request );
+		$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
+	}
+
+	public function test_set_value_invalid_value_multiple() {
+		register_meta( 'post', 'my_meta_key', array(
+			'show_in_rest' => true,
+			'single' => false,
+			'type' => 'string',
+		));
+
+		$this->grant_write_permission();
+
+		$data = array(
+			'meta' => array(
+				'my_meta_key' => array( array( 'a' ) ),
+			),
+		);
+		$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
+		$request->set_body_params( $data );
+
+		$response = $this->server->dispatch( $request );
+		$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
+	}
+
+	public function test_set_value_sanitized() {
+		register_meta( 'post', 'my_meta_key', array(
+			'show_in_rest' => true,
+			'single' => true,
+			'type' => 'integer',
+		));
+
+		$this->grant_write_permission();
+
+		$data = array(
+			'meta' => array(
+				'my_meta_key' => '1', // Set to a string.
+			),
+		);
+		$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
+		$request->set_body_params( $data );
+
+		$response = $this->server->dispatch( $request );
+		$data = $response->get_data();
+		$this->assertEquals( 1, $data['meta']['my_meta_key'] );
+	}
+
+	public function test_set_value_csv() {
+		register_meta( 'post', 'my_meta_key', array(
+			'show_in_rest' => true,
+			'single' => false,
+			'type' => 'integer',
+		));
+
+		$this->grant_write_permission();
+
+		$data = array(
+			'meta' => array(
+				'my_meta_key' => '1,2,3', // Set to a string.
+			),
+		);
+		$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
+		$request->set_body_params( $data );
+
+		$response = $this->server->dispatch( $request );
+		$data = $response->get_data();
+		$this->assertEquals( array( 1, 2, 3 ), $data['meta']['my_meta_key'] );
+	}
+
 	/**
 	 * @depends test_set_value_multiple
 	 */
@@ -515,6 +631,7 @@
 		$this->assertErrorResponse( 'rest_meta_database_error', $response, 500 );
 	}
 
+
 	public function test_delete_value() {
 		add_post_meta( self::$post_id, 'test_single', 'val1' );
 		$current = get_post_meta( self::$post_id, 'test_single', true );
@@ -618,6 +735,7 @@
 		$this->assertArrayNotHasKey( 'test_no_rest', $meta_schema );
 		$this->assertArrayNotHasKey( 'test_rest_disabled', $meta_schema );
 		$this->assertArrayNotHasKey( 'test_invalid_type', $meta_schema );
+		$this->assertArrayNotHasKey( 'test_no_type', $meta_schema );
 	}
 
 	/**
Index: tests/phpunit/tests/rest-api/rest-schema-sanitization.php
===================================================================
--- tests/phpunit/tests/rest-api/rest-schema-sanitization.php	(revision 39184)
+++ tests/phpunit/tests/rest-api/rest-schema-sanitization.php	(working copy)
@@ -76,6 +76,20 @@
 		$this->assertEquals( array( 1 ), rest_sanitize_value_from_schema( array( '1' ), $schema ) );
 	}
 
+	public function test_type_array_nested() {
+		$schema = array(
+			'type' => 'array',
+			'items' => array(
+				'type' => 'array',
+				'items' => array(
+					'type' => 'number',
+				),
+			),
+		);
+		$this->assertEquals( array( array( 1 ), array( 2 ) ), rest_sanitize_value_from_schema( array( array( 1 ), array( 2 ) ), $schema ) );
+		$this->assertEquals( array( array( 1 ), array( 2 ) ), rest_sanitize_value_from_schema( array( array( '1' ), array( '2' ) ), $schema ) );
+	}
+
 	public function test_type_array_as_csv() {
 		$schema = array(
 			'type' => 'array',
@@ -110,4 +124,32 @@
 		$this->assertEquals( array( 'ribs', 'chicken' ), rest_sanitize_value_from_schema( 'ribs,chicken', $schema ) );
 		$this->assertEquals( array( 'chicken', 'coleslaw' ), rest_sanitize_value_from_schema( 'chicken,coleslaw', $schema ) );
 	}
+
+	public function test_type_array_is_associative() {
+		$schema = array(
+			'type' => 'array',
+			'items' => array(
+				'type' => 'string',
+			),
+		);
+		$this->assertEquals( array( '1', '2' ), rest_sanitize_value_from_schema( array( 'first' => '1', 'second' => '2' ), $schema ) );
+	}
+
+	public function test_type_unknown() {
+		$schema = array(
+			'type' => 'lalala',
+		);
+		$this->assertEquals( 'Best lyrics', rest_sanitize_value_from_schema( 'Best lyrics', $schema ) );
+		$this->assertEquals( 1.10, rest_sanitize_value_from_schema( 1.10, $schema ) );
+		$this->assertEquals( 1, rest_sanitize_value_from_schema( 1, $schema ) );
+	}
+
+	public function test_no_type() {
+		$schema = array(
+			'type' => null,
+		);
+		$this->assertEquals( 'Nothing', rest_sanitize_value_from_schema( 'Nothing', $schema ) );
+		$this->assertEquals( 1.10, rest_sanitize_value_from_schema( 1.10, $schema ) );
+		$this->assertEquals( 1, rest_sanitize_value_from_schema( 1, $schema ) );
+	}
 }
Index: tests/phpunit/tests/rest-api/rest-schema-validation.php
===================================================================
--- tests/phpunit/tests/rest-api/rest-schema-validation.php	(revision 39184)
+++ tests/phpunit/tests/rest-api/rest-schema-validation.php	(working copy)
@@ -104,6 +104,19 @@
 		$this->assertWPError( rest_validate_value_from_schema( array( true ), $schema ) );
 	}
 
+	public function test_type_array_nested() {
+		$schema = array(
+			'type' => 'array',
+			'items' => array(
+				'type' => 'array',
+				'items' => array(
+					'type' => 'number',
+				),
+			),
+		);
+		$this->assertTrue( rest_validate_value_from_schema( array( array( 1 ), array( 2 ) ), $schema ) );
+	}
+
 	public function test_type_array_as_csv() {
 		$schema = array(
 			'type' => 'array',
@@ -139,4 +152,23 @@
 		$this->assertTrue( rest_validate_value_from_schema( 'ribs,chicken', $schema ) );
 		$this->assertWPError( rest_validate_value_from_schema( 'chicken,coleslaw', $schema ) );
 	}
+
+	public function test_type_array_is_associative() {
+		$schema = array(
+			'type'  => 'array',
+			'items' => array(
+				'type' => 'string',
+			),
+		);
+		$this->assertWPError( rest_validate_value_from_schema( array( 'first' => '1', 'second' => '2' ), $schema ) );
+	}
+
+	public function test_type_unknown() {
+		$schema = array(
+			'type'  => 'lalala',
+		);
+		$this->assertTrue( rest_validate_value_from_schema( 'Best lyrics', $schema ) );
+		$this->assertTrue( rest_validate_value_from_schema( 1, $schema ) );
+		$this->assertTrue( rest_validate_value_from_schema( array(), $schema ) );
+	}
 }
