概述

架构(schema)是描述数据结构化的元数据。大多数数据库都实现了某种形式的架构,使我们能够以更结构化的方式理解数据。WordPress REST API 利用 JSON Schema 来处理其数据的结构化。您可以实现不使用架构的端点,但这样会错过很多功能。具体是否使用取决于您的最佳选择。

JSON

首先,让我们谈谈 JSON。JSON 是一种人类可读的数据格式,类似于 JavaScript 对象。JSON 代表 JavaScript Object Notation。JSON 正以惊人的速度流行起来,似乎正在席卷数据结构领域。WordPress REST API 使用一种名为 JSON Schema 的特殊 JSON 规范。要了解有关 JSON Schema 的更多信息,请查看 JSON Schema 网站 以及这份更易于理解的 JSON Schema 入门指南。架构为我们带来了许多好处:改进测试、可发现性以及整体更好的结构。让我们看看一个 JSON 数据块。

{
    "shouldBeArray": 'LOL definitely not an array',
    "shouldBeInteger": ['lolz', 'you', 'need', 'schema'],
    "shouldBeString": 123456789
}

JSON 解析器可以毫无问题地处理该数据,并且不会抱怨任何问题,因为它是有效的 JSON。客户端和服务器对数据和预期内容一无所知,它们只看到 JSON。通过实施架构,我们实际上可以简化代码库。架构将帮助我们更好地结构化数据,使我们的应用程序能够更容易地推理与 WordPress REST API 的交互。WordPress REST API 并不强制您使用架构,但鼓励这样做。架构数据以两种方式集成到 API 中;资源架构和我们注册的参数架构。

资源架构

资源的架构指示特定对象中存在哪些字段。当我们注册路由时,还可以为路由指定资源架构。让我们看看在 JSON Schema 的 PHP 表示中,简单的评论架构可能是什么样子。

// Register our routes.
function prefix_register_my_comment_route() {
    register_rest_route( 'my-namespace/v1', '/comments', array(
        // Notice how we are registering multiple endpoints the 'schema' equates to an OPTIONS request.
        array(
            'methods'  => 'GET',
            'callback' => 'prefix_get_comment_sample',
        ),
        // Register our schema callback.
        'schema' => 'prefix_get_comment_schema',
    ) );
}

add_action( 'rest_api_init', 'prefix_register_my_comment_route' );

/**
 * Grabs the five most recent comments and outputs them as a rest response.
 *
 * @param WP_REST_Request $request Current request.
 */
function prefix_get_comment_sample( $request ) {
    $args = array(
        'number' => 5,
    );
    $comments = get_comments( $args );

    $data = array();

    if ( empty( $comments ) ) {
        return rest_ensure_response( $data );
    }

    foreach ( $comments as $comment ) {
        $response = prefix_rest_prepare_comment( $comment, $request );
        $data[] = prefix_prepare_for_collection( $response );
    }

    // Return all of our comment response data.
    return rest_ensure_response( $data );
}

/**
 * Matches the comment data to the schema we want.
 *
 * @param WP_Comment $comment The comment object whose response is being prepared.
 */
function prefix_rest_prepare_comment( $comment, $request ) {
    $comment_data = array();

    $schema = prefix_get_comment_schema();

    // We are also renaming the fields to more understandable names.
    if ( isset( $schema['properties']['id'] ) ) {
        $comment_data['id'] = (int) $comment->comment_ID;
    }

    if ( isset( $schema['properties']['author'] ) ) {
        $comment_data['author'] = (int) $comment->user_id;
    }

    if ( isset( $schema['properties']['content'] ) ) {
        $comment_data['content'] = apply_filters( 'comment_text', $comment->comment_content, $comment );
    }

    return rest_ensure_response( $comment_data );
}

/**
 * Prepare a response for inserting into a collection of responses.
 *
 * This is copied from WP_REST_Controller class in the WP REST API v2 plugin.
 *
 * @param WP_REST_Response $response Response object.
 * @return array Response data, ready for insertion into collection data.
 */
function prefix_prepare_for_collection( $response ) {
    if ( ! ( $response instanceof WP_REST_Response ) ) {
        return $response;
    }

    $data  = (array) $response->get_data();
    $links = rest_get_server()::get_compact_response_links( $response );

    if ( ! empty( $links ) ) {
        $data['_links'] = $links;
    }

    return $data;
}

/**
 * Get our sample schema for comments.
 */
function prefix_get_comment_schema() {
    $schema = array(
        // This tells the spec of JSON Schema we are using which is draft 4.
        '$schema'              => 'http://json-schema.org/draft-04/schema#',
        // The title property marks the identity of the resource.
        'title'                => 'comment',
        'type'                 => 'object',
        // In JSON Schema you can specify object properties in the properties attribute.
        'properties'           => array(
            'id' => array(
                'description'  => esc_html__( 'Unique identifier for the object.', 'my-textdomain' ),
                'type'         => 'integer',
                'context'      => array( 'view', 'edit', 'embed' ),
                'readonly'     => true,
            ),
            'author' => array(
                'description'  => esc_html__( 'The id of the user object, if author was a user.', 'my-textdomain' ),
                'type'         => 'integer',
            ),
            'content' => array(
                'description'  => esc_html__( 'The content for the object.', 'my-textdomain' ),
                'type'         => 'string',
            ),
        ),
    );

    return $schema;
}

如果您注意到的话,每个评论资源现在都与我们指定的架构相匹配。我们在 prefix_rest_prepare_comment() 中进行了此切换。通过为我们的资源创建架构,我们现在可以通过发出 OPTIONS 请求来查看此架构。为什么这很有用?如果我们希望其他语言(例如 JavaScript)解释我们的数据并验证来自端点的数据,JavaScript 需要知道我们的数据结构化方式。当我们提供架构时,我们为其他作者以及我们自己打开了大门,以便以一致的方式在我们的端点上构建。

架构提供机器可读的数据,因此任何能够读取 JSON 的东西都有可能理解它正在查看的数据类型。当我们通过向 https://ourawesomesite.com/wp-json/` 发出 GET 请求查看 API 索引时,我们将收到我们 API 的架构,使其他人能够编写客户端库来解释我们的数据。这种读取架构数据的过程称为发现。当我们为资源提供架构时,我们通过对该路由发出 OPTIONS 请求使该资源可被发现。公开资源架构只是我们架构拼图中的一部分。我们还希望为我们的注册参数使用架构。

参数架构

当我们为端点注册请求参数时,也可以使用 JSON Schema 来提供关于参数应该是什么样的数据信息。这使得我们可以编写验证库,随着我们端点的扩展而重复使用。架构在前期需要更多工作,但如果您要编写将会增长的生产应用程序,您绝对应该考虑使用架构。让我们看看使用参数架构和验证的示例。

// 注册我们的路由。
function prefix_register_my_arg_route() {
    register_rest_route( 'my-namespace/v1', '/schema-arg', array(
        // 在这里我们注册我们的端点。
        array(
            'methods'  => 'GET',
            'callback' => 'prefix_get_item',
            'args' => prefix_get_endpoint_args(),
        ),
    ) );
}

// 将注册钩子到 'rest_api_init' 钩子中。
add_action( 'rest_api_init', 'prefix_register_my_arg_route' );

/**
 * 作为 REST 响应返回请求参数 `my-arg`。
 *
 * @param WP_REST_Request $request 当前请求。
 */
function prefix_get_item( $request ) {
    // 如果我们在 schema 中没有使用 required,当 my arg 未设置时会抛出错误。
    return rest_ensure_response( $request['my-arg'] );
}

/**
 * 获取此示例端点的参数 schema。
 */
function prefix_get_endpoint_args() {
    $args = array();

    // 在这里我们添加 JSON Schema 的 PHP 表示形式。
    $args['my-arg'] = array(
        'description'       => esc_html__( '这是我们的端点返回的参数。', 'my-textdomain' ),
        'type'              => 'string',
        'validate_callback' => 'prefix_validate_my_arg',
        'sanitize_callback' => 'prefix_sanitize_my_arg',
        'required'          => true,
    );

    return $args;
}

/**
 * 用于 `my-arg` 参数的验证回调函数。
 *
 * @param mixed           $value   my-arg 参数的值。
 * @param WP_REST_Request $request 当前请求对象。
 * @param string          $param   参数名称,在本例中为 'my-arg'。
 * @return true|WP_Error 如果数据有效则返回 true,否则返回 WP_Error。
 */
function prefix_validate_my_arg( $value, $request, $param ) {
    $attributes = $request->get_attributes();

    if ( isset( $attributes['args'][ $param ] ) ) {
        $argument = $attributes['args'][ $param ];
        // 检查确保我们的参数是字符串类型。
        if ( 'string' === $argument['type'] && ! is_string( $value ) ) {
            return new WP_Error( 'rest_invalid_param', sprintf( esc_html__( '%1$s 不是 %2$s 类型', 'my-textdomain' ), $param, 'string' ), array( 'status' => 400 ) );
        }
    } else {
        // 这段代码不会执行,因为我们已将此参数指定为必填项。
        // 如果我们重用此验证回调且没有指定必填参数,则会触发此代码。
        return new WP_Error( 'rest_invalid_param', sprintf( esc_html__( '%s 未注册为请求参数。', 'my-textdomain' ), $param ), array( 'status' => 400 ) );
    }

    // 如果运行到这里,说明数据有效。
    return true;
}

/**
 * 用于 `my-arg` 参数的清理回调函数。
 *
 * @param mixed           $value   my-arg 参数的值。
 * @param WP_REST_Request $request 当前请求对象。
 * @param string          $param   参数名称,在本例中为 'my-arg'。
 * @return mixed|WP_Error 清理后的值,或者如果数据无法清理则返回 WP_Error。
 */
function prefix_sanitize_my_arg( $value, $request, $param ) {
    $attributes = $request->get_attributes();

    if ( isset( $attributes['args'][ $param ] ) ) {
        $argument = $attributes['args'][ $param ];
        // 检查确保我们的参数是字符串类型。
        if ( 'string' === $argument['type'] ) {
            return sanitize_text_field( $value );
        }
    } else {
        // 这段代码不会执行,因为我们已将此参数指定为必填项。
        // 如果我们重用此验证回调且没有指定必填参数,则会触发此代码。
        return new WP_Error( 'rest_invalid_param', sprintf( esc_html__( '%s 未注册为请求参数。', 'my-textdomain' ), $param ), array( 'status' => 400 ) );
    }

    // 如果运行到这里,说明出错了,不要使用用户输入。
    return new WP_Error( 'rest_api_sad', esc_html__( '发生严重错误。', 'my-textdomain' ), array( 'status' => 500 ) );
}

在上面的示例中,我们已经抽象化了 `'my-arg'` 名称的使用。我们可以将这些验证和清理函数用于任何其他我们已为其指定 schema 且应为字符串类型的参数。随着您的代码库和端点的扩展,schema 将有助于保持您的代码轻量级且易于维护。没有 schema 也可以进行验证和清理,但跟踪哪些函数应该验证哪些内容会更加困难。通过向请求参数添加 schema,我们还可以向客户端暴露我们的参数 schema,以便构建客户端侧的验证库,这可以通过防止无效请求发送到 API 来帮助提高性能。

如果您对使用 schema 感到不安,仍然可以为每个参数拥有 validate/sanitize 回调函数,在某些情况下,执行自定义验证可能是最合适的选择。

总结

Schema 在某些时候可能看起来有些多余,甚至像是不必要的工作,但如果您希望端点具有可维护性、可发现性和易于扩展性,那么使用 schema 是必不可少的。Schema 还有助于为您的端点进行自我文档化,既针对人类也针对计算机!

JSON Schema 基础

WordPress 实现了一个验证器,它使用了 JSON Schema Version 4 规范 的一个子集。推荐阅读 RFC 以更深入地理解 JSON Schema 的工作原理,但本文档将描述 JSON Schema 的基础知识以及 WordPress 支持的功能。

API

REST API 定义了两个用于使用 JSON Schema 的主要函数:rest_validate_value_from_schema 和 rest_sanitize_value_from_schema。这两个函数都接受请求数据作为第一个参数,参数的 schema 定义作为第二个参数,可选地接受参数名称作为第三个参数。验证函数根据数据是否成功通过 schema 验证,返回 true 或 WP_Error 实例。清理函数返回传递给函数的数据的清理形式,或者如果数据无法安全清理则返回 WP_Error 实例。

调用这些函数时,您应该务必首先使用 rest_validate_value_from_schema 验证数据,然后如果该函数返回 true,再使用 rest_sanitize_value_from_schema 清理数据。不使用两者可能会导致端点存在安全漏洞。

如果您的端点是使用 `WP_REST_Controller` 的子类 实现的,WP_REST_Controller::get_endpoint_args_for_item_schema 方法将自动将您的参数标记为使用内置的验证和清理回调函数。因此,无需手动指定回调函数。

如果您的端点不遵循控制器类模式,从 WP_REST_Controller::get_collection_params() 返回的 args,或任何其他未指定回调函数的情况,WP_REST_Request 对象将使用 rest_parse_request_arg 函数应用清理和验证。重要的是,仅当未定义 sanitize_callback 时才应用此操作。因此,如果您为参数定义指定了自定义 sanitize_callback,则内置的 JSON Schema 验证将不适用。如果需要此验证,您应该在参数定义中手动指定 rest_validate_request_arg 作为 validate_callback。

缓存 Schema

Scha 可能很复杂,生成可能需要时间。您应该考虑在插件的自定义端点中缓存生成的 schema,以避免重复生成相同的 schema 对象。

如果您是使用 `WP_REST_Controller` 的子类 定义端点,这可能看起来像这样:


/** * 检索符合 JSON Schema 的附件 schema。 * * @return array 项目 schema 数组。 */ public function get_item_schema() { // 只要可用就返回缓存副本。 if ( $this->schema ) { return $this->add_additional_fields_schema( $this->schema ); } $schema = parent::get_item_schema(); // 向 Schema 添加端点特定属性。 $schema['properties']['field_name'] = array( /* ... */ ); $schema['properties']['etcetera'] = array( /* ... */ ); // 在端点实例上缓存生成的 schema。 $this->schema = $schema; return $this->add_additional_fields_schema( $this->schema ); }

此模式于版本 5.3 引入 WordPress 核心,见 #47871,在生成某些 API 响应时带来了高达 40% 的速度提升。

Scha 文档

基本 schema 文档由几个属性组成。

  • $schema 指向一个元模式的引用,用于描述文档所使用的规范版本。
  • title 模式的标题。通常这是一个人类可读的标签,但在 WordPress 中,它是机器可读的字符串。例如,posts 端点的标题为'post'。Comments 的标题为'comment'。
  • type 指代所描述值的类型。这可以是七种原始类型中的任意一种。在 WordPress 中,顶级类型几乎总是 object,即使对于返回对象数组的集合端点也是如此。
  • properties 包含在该对象中的已知属性列表及其定义。每个属性定义本身也是一个模式,但没有 $schema 顶级属性,更准确地说是子模式。

原始类型

JSON Schema 定义了七个允许的 原始类型。

  • string 字符串值。
  • null null 值。
  • number 任意数字。允许小数。等同于 PHP 中的 float。
  • integer 数字,但不允许小数或指数。
  • boolean true 或 false 值。
  • array 值列表。这等同于 JavaScript 数组。在 PHP 中称为数值数组,或没有定义键的数组。
  • object 键到值的映射。这等同于 JavaScript 对象。在 PHP 中称为 关联数组,或具有定义键的数组。

值的原始类型使用 type 关键字指定。例如,这是您使用 JSON Schema 定义 string 值的方式。

array(
    'type' => 'string',
);

JSON Schema 允许定义可以是多种类型的值。例如,这是您定义既可以是 string 又可以是 boolean 的值的方式。

array(
    'type' => array( 'boolean', 'string' ),  
);

类型转换

由于 WordPress REST API 接受 URL 表单编码 数据,无论是作为 POST 主体还是 URL 的查询部分,许多原始类型都会执行类型转换以将这些字符串值转换为其正确的原生类型。

  • string 仅允许符合 is_string 的字符串。
  • null 仅接受正确类型的 null。这意味着无法在 URL 中提交 null 值或作为 URL 表单编码的 POST 主体,必须使用 JSON 请求体。
  • number 允许通过 is_numeric 检查的浮点数、整数和字符串。值将被转换为 float。
  • integer 整数或可以 转换为 float 且小数部分等于 0 的字符串。
  • boolean 布尔值、整数 0 和 1,或字符串 "0"、"1"、"false" 和 "true"。0 被视为 false,1 被视为 true。
  • array 根据 wp_is numeric_array 的数值数组或字符串。如果字符串是逗号分隔的,它将拆分为数组,否则它将是包含该字符串值的数组。例如:"red,yellow" 变为 array( "red", "yellow" ),而 "blue" 变为 array( "blue" )。
  • object 数组、stdClass 对象、实现 JsonSerializable 的对象或空字符串。值将转换为原生 PHP 数组。

当使用多种类型时,类型将按照它们指定的顺序进行评估。这可能会影响您的 REST API 端点接收到的清理后数据。例如,在前面的示例中,如果提交的值为 "1",它将被清理为布尔值 true。但是,如果顺序颠倒,该值将保持为字符串 "1"。

JSON Schema 规范允许定义没有 type 字段的模式。然而,WordPress 实现要求定义 type,如果省略类型,将发出 _doing_it_wrong 通知。

格式

这七种原始类型仅仅是 原始 类型,那么如何定义更复杂的值类型呢?一种方法是使用 format 关键字。format 关键字允许对具有良好定义结构的值进行额外的语义级验证。

例如,要要求日期值,您将使用 date-time 格式。

array(
    'type'   => 'string',
    'format' => 'date-time',
);

WordPress 支持以下格式:

  • date-time 根据 RFC3339 格式化的日期。
  • uri 根据 esc_url_raw 的 uri。
  • email 根据 is_email 的电子邮件地址。
  • ip v4 或 v6 IP 地址。
  • uuid 任何版本的 uuid。
  • hex-color 带有前导 # 的 3 或 6 位十六进制颜色。

即使值是空字符串,也必须与其格式匹配。如果需要允许“空”值,请添加 null 作为可能的类型。

例如,以下模式将允许 127.0.0.1 或 null 作为可能的值。

array(
    'type'   => array( 'string', 'null' ),
    'format' => 'ip',
);

字符串

string 类型支持三个附加关键字。

minLength 和 maxLength

minLength 和 maxLength 关键字可用于限制字符串的可接受长度。重要的是多字节字符计为一个字符,且边界是包含性的。

例如,给定以下模式,ab、abc 和 abcd 是有效的,而 a 和 abcde 是无效的。

array(
    'type'      => 'string',
    'minLength' => 2,
    'maxLength' => 4,
);

exclusiveMinimum 和 exclusiveMaximum 关键字不适用,它们仅适用于数字。

pattern

JSON Schema 关键字 pattern 可用于验证字符串字段是否匹配正则表达式。

例如,给定以下模式,#123 是有效的,但 #abc 不是。

array(
    'type'    => 'string',
    'pattern' => '#[0-9]+',
);

正则表达式不会自动锚定。不支持正则表达式标志,例如使匹配不区分大小写的 /i。

JSON Schema RFC 建议您仅限于以下正则表达式功能,以便模式尽可能在不同的编程语言之间互操作。

  • 由 JSON 规范 [RFC4627] 定义的单个 Unicode 字符。
  • 简单字符类 [abc],范围字符类 [a-z]。
  • 补码字符类 [^abc]、[^a-z]。
  • 简单量词:+(一个或多个)、*(零个或多个)、?(零个或一个),以及它们的惰性版本 +?、*?、??。
  • 范围量词:{x}(恰好 x 次)、{x,y}(至少 x 次,最多 y 次)、{x,}(x 次或更多),以及它们的惰性版本。
  • 输入开始 ^ 和输入结束 $ 锚点。
  • 简单分组 (...) 和交替 |。

模式应根据 ECMA 262 正则表达式方言有效。

数字

number 和 integer 类型支持四个附加关键字。

minimum 和 maximum

minimum 和 maximum 关键字允许限制可接受数字的范围。例如,2 根据此模式是有效的,但 0 和 4 则不是。

array(
    'type' => 'integer',
    'minimum' => 1,
    'maximum' => 3,
);

JSON Schema 还允许使用 exclusiveMinimum 和 exclusiveMaximum 关键字来分别表示该值不能等于定义的 minimum 或 maximum。例如,在这种情况下只有 2 才是可接受的值。

array(
    'type'             => 'integer',
    'minimum'          => 1,
    'exclusiveMinimum' => true,
    'maximum'          => 3,
    'exclusiveMaximum' => true,
);

multipleOf

multipleOf 关键字允许断言整数或数字类型是给定数字的倍数。例如,此模式将仅接受偶数整数。

array(
    'type'       => 'integer',
    'multipleOf' => 2,
);

multipleOf 也支持小数。例如,此模式可用于接受最多保留一位小数的百分比。

array(
    'type'       => 'number',
    'minimum'    => 0,
    'maximum'    => 100,
    'multipleOf' => 0.1,
);

Arrays

指定 type 为 array 要求数据必须是数组,但这只是验证的一半。您还需要强制规定数组中每个项的格式。这是通过使用 items 关键字指定每个数组项必须遵循的 JSON Schema 来实现的。

例如,以下模式要求一个 IP 地址数组。

array(
    'type'  => 'array',
    'items' => array(
        'type'   => 'string',
        'format' => 'ip',
    ),
);

这将通过验证。

[ "127.0.0.1", "255.255.255.255" ]

而以下内容将失败验证。

[ "127.0.0.1", 5 ]

items 模式可以是任何模式,它本身甚至可以是一个数组!

array(
    'type'  => 'array',
    'items' => array(
        'type'  => 'array',
        'items' => array(
            'type'   => 'string',
            'format' => 'hex-color',
        ),
    ),
);

这将通过验证。

[
  [ "#ff6d69", "#fecc50" ],
  [ "#0be7fb" ]
]

而以下内容将失败验证。

[
  [ "#ff6d69", "#fecc50" ],
  "george"
]

minItems and maxItems

minItems 和 maxItems 关键字可用于限制数组中包含的可接受项目数量。

例如,给定以下模式,[ "a" ] 和 [ "a", "b" ] 是有效的,而 [] 和 [ "a", "b", "c" ] 是无效的。

array(
    'type'     => 'array',
    'minItems' => 1,
    'maxItems' => 2,
    'items'    => array(
        'type' => 'string',
    ),
);

exclusiveMinimum 和 exclusiveMaximum 关键字不适用,它们仅对 number 和 integer 类型有效。

uniqueItems

uniqueItems 关键字可用于要求数组中的所有项都是唯一的。

例如,给定以下模式,[ "a", "b" ] 是有效的,而 [ "a", "a" ] 则不是。

array(
    'type'        => 'array',
    'uniqueItems' => true,
    'items'       => array(
        'type' => 'string',
    ),
);
Uniqueness

不同类型的项被视为唯一,例如,"1"、1 和 1.0 是不同的值。

当比较数组时,项的顺序很重要。因此,给定的数组被认为具有所有唯一项。

[
  [ "a", "b" ],
  [ "b", "a" ]
]

当比较对象时,成员出现的顺序无关紧要。因此,给定的数组被认为具有重复项,因为值是相同的,只是出现的顺序不同。

[
  { 
    "a": 1,
    "b": 2
  },
  {
    "b": 2,
    "a": 1
  }
]

唯一性检查在 rest_validate_value_from_schema 和 rest_sanitize_value_from_schema 中进行。这是为了防止出现这样的情况:在应用清理之前项被认为是唯一的,但在清理之后项会收敛为相同的值。

例如,考虑以下模式:

array(
    'type' => 'array',
    'uniqueItems' => true,
    'items' => array(
        'type' => 'string',
        'format' => 'uri',
    ),
);

带有 [ "https://example.org/hello world", "https://example.org/hello%20world" ] 的请求将通过验证,因为每个字符串值都不同。然而,在 esc_url_raw 将第一个 URL 中的空格转换为 %20 后,这些值将变得相同。

在这种情况下,rest_sanitize_value_from_schema 将返回错误。因此,您应该始终验证和清理参数。

Objects

指定 type 为 object 要求数据必须是对象,但这只是验证的一半。您还需要强制规定对象每个属性的格式。这是通过使用 properties 关键字指定属性名称到 JSON Schema 的映射(对象成员必须遵循)来实现的。

例如,以下模式要求一个对象,其中属性 name 是字符串,color 是十六进制颜色。

array(
    'type'       => 'object',
    'properties' => array(
        'name'  => array(
            'type' => 'string',
        ),
        'color' => array(
            'type'   => 'string',
            'format' => 'hex-color',        
        ),
    ),
);

这将通过验证。

{
  "name": "Primary",
  "color": "#ff6d69"
}

而以下内容将失败验证。

{
  "name": "Primary",
  "color": "orange"
}

Required Properties

默认情况下,列出的任何对象属性都是可选的,所以虽然可能出乎意料,但以下内容对于上述模式也会通过验证。

{
  "name": "Primary"
}

有两种机制可以要求提供某个属性。

Version 3 Syntax

虽然 WordPress 主要遵循 JSON Schema Version 4,但它在定义所需属性的语法上有所不同。主要方式是使用 JSON Schema Version 3 语法,通过在每个属性的定义中添加 required 关键字来实现。

array(
    'type'       => 'object',
    'properties' => array(
        'name'  => array(
            'type'     => 'string',
            'required' => true,
        ),
        'color' => array(
            'type'     => 'string',
            'format'   => 'hex-color',
            'required' => true,        
        ),
    ),
);
Version 4 syntax

WordPress 还支持 JSON Schema Version 4 所需的属性语法,其中对象的所需属性列表被定义为属性名称数组。这在指定元值具有所需属性列表时特别有用。

给定以下元字段。

register_post_meta( 'post', 'fixed_in', array(
    'type'         => 'object',
    'show_in_rest' => array(
        'single' => true,
        'schema' => array(
            'required'   => array( 'revision', 'version' ),
            'type'       => 'object',
            'properties' => array(
                'revision' => array(
                    'type' => 'integer',
                ),
                'version'  => array(
                    'type' => 'string',
                ),
            ),
        ),
    ),
) );

以下请求将失败验证。

{
    "title": "Check required properties",
    "content": "We should check that required properties are provided",
    "meta": {
        "fixed_in": {
            "revision": 47089
        }
    }
}

如果完全省略了 fixed_in 元字段,则不会生成错误。定义了所需属性列表的对象并不表示该对象本身必须提交。只是如果包含该对象,则所列属性也必须包含在内。

Version 4 语法不支持 WP_REST_Controller::get_item_schema() 中端点顶层模式的 required 属性。给定以下模式,用户可以成功提交不包含 title 或 content 属性的请求。这是因为模式文档本身不用于验证,而是转换为参数定义列表。

array(
    '$schema'    => 'http://json-schema.org/draft-04/schema#',
    'title'      => 'my-endpoint',
    'type'       => 'object',
    'required'   => array( 'title', 'content' ),
    'properties' => array(
        'title'   => array(
            'type' => 'string',
        ),
        'content' => array(
            'type' => 'string',
        ),
    ),
);

additionalProperties

或许有些反直觉,默认情况下 JSON Schema 也允许提供未在模式中指定的额外属性。因此,以下内容可以通过验证。

{
  "name": "Primary",
  "color": "#ff6d69",
  "description": "The primary color to use in the theme."
}

这可以使用 additionalProperties 关键字进行自定义。将 additionalProperties 设置为 false 将拒绝包含未知属性的数据。

array(
    'type'                 => 'object',
    'additionalProperties' => false,
    'properties'           => array(
        'name'  => array(
            'type' => 'string',
        ),
        'color' => array(
            'type'   => 'string',
            'format' => 'hex-color',        
        ),
    ),
);

additionalProperties 关键字也可以设置为它自己的模式。这将要求任何未知属性的值都匹配给定的模式。

一种有用的方式是当你想要接受一组每个都有自己唯一键的值列表时。例如:

array(
    'type'                 => 'object',
    'properties'           => array(),
    'additionalProperties' => array(
        'type'       => 'object',
        'properties' => array(
            'name'  => array(
                'type'     => 'string',
                'required' => true,
            ),
            'color' => array(
                'type'     => 'string',
                'format'   => 'hex-color',
                'required' => true,        
            ),
        ),    
    ),
);

这将通过验证。

{
  "primary": {
    "name": "Primary",
    "color": "#ff6d69"
  },
  "secondary": {
    "name": "Secondary",
    "color": "#fecc50"
  }
}

而以下内容将无法通过验证。

{
  "primary": {
    "name": "Primary",
    "color": "#ff6d69"
  },
  "secondary": "#fecc50"
}

patternProperties

patternProperties 关键字与 additionalProperties 关键字类似,但允许断言属性匹配正则表达式模式。该关键字是一个对象,其中每个属性都是一个正则表达式模式,其值是用来验证匹配该模式的属性的 JSON Schema。

例如,此模式要求每个值都是十六进制颜色,且属性只能包含“单词”字符。

array(
  'type'                 => 'object',
  'patternProperties'    => array(
    '^\\w+$' => array(
      'type'   => 'string',
      'format' => 'hex-color',
    ),
  ),
  'additionalProperties' => false,
);

这将通过验证。

{
  "primary": "#ff6d69",
  "secondary": "#fecc50"
}

而以下内容将无法通过验证。

{
  "primary": "blue",
  "$secondary": "#fecc50"
}

当 REST API 验证 patternProperties 模式时,如果某个属性不匹配任何模式,则该属性将被允许且不对其内容应用任何验证。虽然这可能令人困惑,但其行为与 properties 关键字相同。如果不希望有此逻辑,请向模式添加 additionalProperties 以禁止不匹配的属性。

minProperties and maxProperties

minItems 和 maxItems 关键字可用于 array 类型。minProperties 和 maxProperties 为 object 类型引入了相同的功能。当使用 additionalProperties 来拥有具有唯一键的对象列表时,这可能很有帮助。

array(
  'type'                 => 'object',
  'additionalProperties' => array(
    'type'   => 'string',
    'format' => 'hex-color',
  ),
  'minProperties'        => 1,
  'maxProperties'        => 3,
);

这将通过验证。

{
  "primary": "#52accc",
  "secondary": "#096484"
}

而以下内容将无法通过验证。

{
  "primary": "#52accc",
  "secondary": "#096484",
  "tertiary": "#07526c"
}

exclusiveMinimum 和 exclusiveMaximum 关键字不适用,它们仅对 number 和 integer 类型有效。

Type agnostic keywords

oneOf and anyOf

这些是高级关键字,允许 JSON Schema 验证器在验证值时从多个模式中选择其中一个。 anyOf 关键字允许值匹配给定模式中的至少一个。而 oneOf 关键字要求值 恰好 匹配一个模式。

例如,此模式允许向端点提交“operations”数组。每个操作可以是“crop”或“rotation”。

array(
    'type'  => 'array',
    'items' => array(
        'oneOf' => array(
            array(
                'title'      => 'Crop',
                'type'       => 'object',
                'properties' => array(
                    'operation' => array(
                        'type' => 'string',
                        'enum' => array(
                            'crop',
                        ),
                    ),
                    'x'         => array(
                        'type' => 'integer',
                    ),
                    'y'         => array(
                        'type' => 'integer',
                    ),
                ),
            ),
            array(
                'title'      => 'Rotation',
                'type'       => 'object',
                'properties' => array(
                    'operation' => array(
                        'type' => 'string',
                        'enum' => array(
                            'rotate',
                        ),
                    ),
                    'degrees'   => array(
                        'type'    => 'integer',
                        'minimum' => 0,
                        'maximum' => 360,
                    ),
                ),
            ),
        ),
    ),
);

REST API 将遍历 oneOf 数组中指定的每个模式并查找匹配项。如果恰好有一个模式匹配,则验证成功。如果多个模式匹配,则验证失败。如果没有模式匹配,则验证器将尝试找到最接近匹配的模式并返回适当的错误消息。

operations[0] is not a valid Rotation. Reason: operations[0][degrees] must be between 0 (inclusive) and 360 (inclusive)

为了生成更有用的错误消息,强烈建议为每个 oneOf 或 anyOf 模式设置 title 属性。

Changelog

WordPress 5.6

  • 支持 multipleOf JSON Schema 关键字。r49063
  • 支持 minProperties 和 maxProperties JSON Schema 关键字。r49053
  • 支持 patternProperties JSON Schema 关键字。r49082
  • 支持 anyOf 和 oneOf JSON Schema 关键字。r49246

WordPress 5.5

  • 改进多类型 JSON Schema 支持。r48306
  • 验证对象时检查是否提供了必需属性。r47809
  • 仅当 type 为 string 时才验证 format 关键字。r48300
  • 支持 uuid JSON Schema 格式。47753
  • 支持 hex-color JSON Schema 格式。r47450
  • 支持 pattern JSON Schema 关键字。r47810
  • 支持 minItems、maxItems 和 uniqueItems JSON Schema 关键字。r47923 r48357
  • 支持 minLength 和 maxLength JSON Schema 关键字。r47627

WordPress 5.4

  • 支持将空字符串转换为空对象的类型转换。r47362

WordPress 5.3

  • 支持 null 基本类型并实现基本的多类型处理。r46249
  • 支持针对模式验证 additionalProperties。r45807

WordPress 4.9

  • 支持 object 基本类型。r41727