diff --git a/wp-includes/rest-api.php b/wp-includes/rest-api.php
index 91b7343f56..dc359aec50 100644
--- a/wp-includes/rest-api.php
+++ b/wp-includes/rest-api.php
@@ -820,80 +820,7 @@ function rest_validate_request_arg( $value, $request, $param ) {
 	}
 	$args = $attributes['args'][ $param ];
 
-	if ( ! empty( $args['enum'] ) ) {
-		if ( ! in_array( $value, $args['enum'], true ) ) {
-			return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: list of valid values */ __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) );
-		}
-	}
-
-	if ( 'integer' === $args['type'] && ! is_numeric( $value ) ) {
-		return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, 'integer' ) );
-	}
-
-	if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) {
-		return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $value, 'boolean' ) );
-	}
-
-	if ( 'string' === $args['type'] && ! is_string( $value ) ) {
-		return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, 'string' ) );
-	}
-
-	if ( isset( $args['format'] ) ) {
-		switch ( $args['format'] ) {
-			case 'date-time' :
-				if ( ! rest_parse_date( $value ) ) {
-					return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ) );
-				}
-				break;
-
-			case 'email' :
-				if ( ! is_email( $value ) ) {
-					return new WP_Error( 'rest_invalid_email', __( 'The email address you provided is invalid.' ) );
-				}
-				break;
-			case 'ipv4' :
-				if ( ! rest_is_ip_address( $value ) ) {
-					return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $value ) );
-				}
-				break;
-		}
-	}
-
-	if ( in_array( $args['type'], array( 'numeric', 'integer' ), true ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) {
-		if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
-			if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
-				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d (exclusive)' ), $param, $args['minimum'] ) );
-			} elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
-				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d (inclusive)' ), $param, $args['minimum'] ) );
-			}
-		} elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
-			if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
-				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d (exclusive)' ), $param, $args['maximum'] ) );
-			} elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
-				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d (inclusive)' ), $param, $args['maximum'] ) );
-			}
-		} elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) {
-			if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
-				if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
-					return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: minimum number, 3: maximum number */ __( '%1$s must be between %2$d (exclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
-				}
-			} elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
-				if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
-					return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: minimum number, 3: maximum number */ __( '%1$s must be between %2$d (inclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
-				}
-			} elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
-				if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
-					return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: minimum number, 3: maximum number */ __( '%1$s must be between %2$d (exclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
-				}
-			} elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
-				if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
-					return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: minimum number, 3: maximum number */ __( '%1$s must be between %2$d (inclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
-				}
-			}
-		}
-	}
-
-	return true;
+	return rest_validate_value_from_schema( $value, $args, $param );
 }
 
 /**
@@ -913,34 +840,7 @@ function rest_sanitize_request_arg( $value, $request, $param ) {
 	}
 	$args = $attributes['args'][ $param ];
 
-	if ( 'integer' === $args['type'] ) {
-		return (int) $value;
-	}
-
-	if ( 'boolean' === $args['type'] ) {
-		return rest_sanitize_boolean( $value );
-	}
-
-	if ( isset( $args['format'] ) ) {
-		switch ( $args['format'] ) {
-			case 'date-time' :
-				return sanitize_text_field( $value );
-
-			case 'email' :
-				/*
-				 * sanitize_email() validates, which would be unexpected
-				 */
-				return sanitize_text_field( $value );
-
-			case 'uri' :
-				return esc_url_raw( $value );
-
-			case 'ipv4' :
-				return sanitize_text_field( $value );
-		}
-	}
-
-	return $value;
+	return rest_sanitize_value_from_schema( $value, $args, $param );
 }
 
 /**
@@ -1084,3 +984,154 @@ function rest_get_avatar_sizes() {
 	 */
 	return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
 }
+
+/**
+ * Validate a value based on a schema.
+ *
+ * @param mixed  $value The value to validate.
+ * @param array  $args  Schema array to use for validation.
+ * @param string $param The parameter name, used in error messages.
+ * @return true|WP_Error
+ */
+function rest_validate_value_from_schema( $value, $args, $param = '' ) {
+	if ( 'array' === $args['type'] ) {
+		if ( ! is_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 ) ) {
+				return $is_valid;
+			}
+		}
+	}
+	if ( ! empty( $args['enum'] ) ) {
+		if ( ! in_array( $value, $args['enum'], true ) ) {
+			return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: list of valid values */ __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) );
+		}
+	}
+
+	if ( in_array( $args['type'], array( 'integer', 'number' ) ) && ! is_numeric( $value ) ) {
+		return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, $args['type'] ) );
+	}
+
+	if ( 'integer' === $args['type'] && round( floatval( $value ) ) !== floatval( $value ) ) {
+		return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, 'integer' ) );
+	}
+
+	if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) {
+		return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $value, 'boolean' ) );
+	}
+
+	if ( 'string' === $args['type'] && ! is_string( $value ) ) {
+		return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, 'string' ) );
+	}
+
+	if ( isset( $args['format'] ) ) {
+		switch ( $args['format'] ) {
+			case 'date-time' :
+				if ( ! rest_parse_date( $value ) ) {
+					return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ) );
+				}
+				break;
+
+			case 'email' :
+				if ( ! is_email( $value ) ) {
+					return new WP_Error( 'rest_invalid_email', __( 'The email address you provided is invalid.' ) );
+				}
+				break;
+			case 'ipv4' :
+				if ( ! rest_is_ip_address( $value ) ) {
+					return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $value ) );
+				}
+				break;
+		}
+	}
+
+	if ( in_array( $args['type'], array( 'number', 'integer' ), true ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) {
+		if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
+			if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
+				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d (exclusive)' ), $param, $args['minimum'] ) );
+			} elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
+				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d (inclusive)' ), $param, $args['minimum'] ) );
+			}
+		} elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
+			if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
+				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d (exclusive)' ), $param, $args['maximum'] ) );
+			} elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
+				return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d (inclusive)' ), $param, $args['maximum'] ) );
+			}
+		} elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) {
+			if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
+				if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
+					return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: minimum number, 3: maximum number */ __( '%1$s must be between %2$d (exclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
+				}
+			} elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
+				if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
+					return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: minimum number, 3: maximum number */ __( '%1$s must be between %2$d (inclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
+				}
+			} elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
+				if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
+					return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: minimum number, 3: maximum number */ __( '%1$s must be between %2$d (exclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
+				}
+			} elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
+				if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
+					return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: minimum number, 3: maximum number */ __( '%1$s must be between %2$d (inclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
+				}
+			}
+		}
+	}
+
+	return true;
+}
+
+/**
+ * Sanitize a value based on a schema.
+ *
+ * @param mixed $value The value to sanitize.
+ * @param array $args  Schema array to use for sanitization.
+ * @return true|WP_Error
+ */
+function rest_sanitize_value_from_schema( $value, $args ) {
+	if ( 'array' === $args['type'] ) {
+		if ( empty( $args['items'] ) ) {
+			return (array) $value;
+		}
+		foreach ( $value as $index => $v ) {
+			$value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'] );
+		}
+		return $value;
+	}
+	if ( 'integer' === $args['type'] ) {
+		return (int) $value;
+	}
+
+	if ( 'number' === $args['type'] ) {
+		return (float) $value;
+	}
+
+	if ( 'boolean' === $args['type'] ) {
+		return rest_sanitize_boolean( $value );
+	}
+
+	if ( isset( $args['format'] ) ) {
+		switch ( $args['format'] ) {
+			case 'date-time' :
+				return sanitize_text_field( $value );
+
+			case 'email' :
+				/*
+				 * sanitize_email() validates, which would be unexpected.
+				 */
+				return sanitize_text_field( $value );
+
+			case 'uri' :
+				return esc_url_raw( $value );
+
+			case 'ipv4' :
+				return sanitize_text_field( $value );
+		}
+	}
+
+	return $value;
+}
diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-controller.php
index b4d9f65517..9bf2624fd9 100644
--- a/wp-includes/rest-api/endpoints/class-wp-rest-controller.php
+++ b/wp-includes/rest-api/endpoints/class-wp-rest-controller.php
@@ -559,7 +559,7 @@ abstract class WP_REST_Controller {
 				$endpoint_args[ $field_id ]['required'] = true;
 			}
 
-			foreach ( array( 'type', 'format', 'enum' ) as $schema_prop ) {
+			foreach ( array( 'type', 'format', 'enum', 'items' ) as $schema_prop ) {
 				if ( isset( $params[ $schema_prop ] ) ) {
 					$endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
 				}
diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
index a9d8f3c557..49d030bad7 100644
--- a/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
+++ b/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
@@ -1971,6 +1971,9 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
 			$schema['properties'][ $base ] = array(
 				'description' => sprintf( __( 'The terms assigned to the object in the %s taxonomy.' ), $taxonomy->name ),
 				'type'        => 'array',
+				'items'       => array(
+					'type'    => 'integer',
+				),
 				'context'     => array( 'view', 'edit' ),
 			);
 			$schema['properties'][ $base . '_exclude' ] = array(
diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php
index 66110efe8c..0f1ead65f2 100644
--- a/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php
+++ b/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php
@@ -288,8 +288,30 @@ class WP_REST_Settings_Controller extends WP_REST_Controller {
 
 		foreach ( $options as $option_name => $option ) {
 			$schema['properties'][ $option_name ] = $option['schema'];
+			$schema['properties'][ $option_name ]['arg_options'] = array(
+				'sanitize_callback' => array( $this, 'sanitize_callback' ),
+			);
 		}
 
 		return $this->add_additional_fields_schema( $schema );
 	}
+
+	/**
+	 * Custom sanitize callback used for all options to allow the use of 'null'.
+	 *
+	 * By default, the schema of settings will throw an error if a value is set to
+	 * `null` as it's not a valid value for something like "type => string". We
+	 * provide a wrapper sanitizer to whitelist the use of `null`.
+	 *
+	 * @param  mixed           $value   The value for the setting.
+	 * @param  WP_REST_Request $request The request object.
+	 * @param  string          $param   The parameter name.
+	 * @return mixed|WP_Error
+	 */
+	public function sanitize_callback( $value, $request, $param ) {
+		if ( is_null( $value ) ) {
+			return $value;
+		}
+		return rest_parse_request_arg( $value, $request, $param );
+	}
 }
diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
index 48d649a10b..d366c70371 100644
--- a/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
+++ b/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
@@ -1006,6 +1006,9 @@ class WP_REST_Users_Controller extends WP_REST_Controller {
 				'roles'           => array(
 					'description' => __( 'Roles assigned to the resource.' ),
 					'type'        => 'array',
+					'items'       => array(
+						'type'    => 'string',
+					),
 					'context'     => array( 'edit' ),
 				),
 				'password'        => array(
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 7a8e3b2bc3..6fa2803807 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -4,7 +4,7 @@
  *
  * @global string $wp_version
  */
-$wp_version = '4.7-beta1-39045';
+$wp_version = '4.7-beta1-39046';
 
 /**
  * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.