架构

最后更新于:2021-11-29 13:22:37

结构

概述

结构是元数据,告诉我们数据的结构。大多数数据库都实现了某种形式的结构,使我们能够以更有条理的方式推理我们的数据。GeChiUI REST API使用JSON结构来处理其数据的结构。您可以在不使用结构的情况下实现端点,但您将错过许多事情。由你来决定什么最适合你。

JSON

首先,让我们谈谈JSON。JSON是一种类似于JavaScript对象的人类可读数据格式。JSON代表JavaScript对象符号。JSON的受欢迎程度正在大幅增长,似乎正在席卷数据结构的世界。GeChiUI REST API对JSON使用一种称为JSON结构的特殊规范。要了解有关JSON结构的更多信息,请查看JSON结构网站,这更容易理解JSON结构的介绍。架构为我们提供了许多好处:改进测试、可发现性和整体更好的结构。让我们看看JSON数据块。

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

JSON解析器将毫无问题地浏览这些数据,也不会抱怨任何事情,因为它是有效的JSON。客户端和服务器对数据一无所知,也不知道他们只看到JSON。通过实现结构,我们实际上可以简化代码库。架构将有助于更好地构建我们的数据,以便我们的应用程序可以更轻松地推理我们与GeChiUI REST API的交互。GeChiUI REST API不强制您使用结构,但鼓励您使用结构。将结构数据集成到API中有两种方式:资源的结构和我们注册参数的结构。

资源结构

资源的结构指示特定对象存在哪些字段。当我们注册路由时,我们还可以指定路由的资源结构。让我们看看简单的评论结构在JSON结构的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 GC_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 GC_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 GC_REST_Controller class in the GC REST API v2 plugin.
 *
 * @param GC_REST_Response $response Response object.
 * @return array Response data, ready for insertion into collection data.
 */
function prefix_prepare_for_collection( $response ) {
    if ( ! ( $response instanceof GC_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 结构 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 结构 you can specify object properties in the properties attribute.
        'properties'           => array(
            'id' => array(
                'description'  => esc_html__( '对象的唯一标识符。', '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__( '对象的内容。', 'my-textdomain' ),
                'type'         => 'string',
            ),
        ),
    );

    return $schema;
}

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

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

争论结构

当我们为端点注册请求参数时,我们还可以使用JSON结构向我们提供有关参数应该是什么的数据。这使我们能够编写验证库,随着端点的扩展,这些库可以重用。结构是前期工作,但如果您要编写一个将增长的生产应用程序,您绝对应该考虑使用结构。让我们看看使用参数结构和验证的示例。

// Register our routes.
function prefix_register_my_arg_route() {
    register_rest_route( 'my-namespace/v1', '/schema-arg', array(
        // Here we register our endpoint.
        array(
            'methods'  => 'GET',
            'callback' => 'prefix_get_item',
            'args' => prefix_get_endpoint_args(),
        ),
    ) );
}

// Hook registration into 'rest_api_init' hook.
add_action( 'rest_api_init', 'prefix_register_my_arg_route' );

/**
 * Returns the request argument `my-arg` as a rest response.
 *
 * @param GC_REST_Request $request Current request.
 */
function prefix_get_item( $request ) {
    // If we didn't use required in the schema this would throw an error when my arg is not set.
    return rest_ensure_response( $request['my-arg'] );
}

/**
 * Get the argument schema for this example endpoint.
 */
function prefix_get_endpoint_args() {
    $args = array();

    // Here we add our PHP representation of JSON 结构.
    $args['my-arg'] = array(
        'description'       => esc_html__( 'This is the argument our endpoint returns.', 'my-textdomain' ),
        'type'              => 'string',
        'validate_callback' => 'prefix_validate_my_arg',
        'sanitize_callback' => 'prefix_sanitize_my_arg',
        'required'          => true,
    );

    return $args;
}

/**
 * Our validation callback for `my-arg` parameter.
 *
 * @param mixed           $value   Value of the my-arg parameter.
 * @param GC_REST_Request $request Current request object.
 * @param string          $param   The name of the parameter in this case, 'my-arg'.
 * @return true|GC_Error True if the data is valid, GC_Error otherwise.
 */
function prefix_validate_my_arg( $value, $request, $param ) {
    $attributes = $request->get_attributes();

    if ( isset( $attributes['args'][ $param ] ) ) {
        $argument = $attributes['args'][ $param ];
        // Check to make sure our argument is a string.
        if ( 'string' === $argument['type'] && ! is_string( $value ) ) {
            return new GC_Error( 'rest_invalid_param', sprintf( esc_html__( '%1$s is not of type %2$s', 'my-textdomain' ), $param, 'string' ), array( 'status' => 400 ) );
        }
    } else {
        // This code won't execute because we have specified this argument as required.
        // If we reused this validation callback and did not have required args then this would fire.
        return new GC_Error( 'rest_invalid_param', sprintf( esc_html__( '%s was not registered as a request argument.', 'my-textdomain' ), $param ), array( 'status' => 400 ) );
    }

    // If we got this far then the data is valid.
    return true;
}

/**
 * Our sanitization callback for `my-arg` parameter.
 *
 * @param mixed           $value   Value of the my-arg parameter.
 * @param GC_REST_Request $request Current request object.
 * @param string          $param   The name of the parameter in this case, 'my-arg'.
 * @return mixed|GC_Error The sanitize value, or a GC_Error if the data could not be sanitized.
 */
function prefix_sanitize_my_arg( $value, $request, $param ) {
    $attributes = $request->get_attributes();

    if ( isset( $attributes['args'][ $param ] ) ) {
        $argument = $attributes['args'][ $param ];
        // Check to make sure our argument is a string.
        if ( 'string' === $argument['type'] ) {
            return sanitize_text_field( $value );
        }
    } else {
        // This code won't execute because we have specified this argument as required.
        // If we reused this validation callback and did not have required args then this would fire.
        return new GC_Error( 'rest_invalid_param', sprintf( esc_html__( '%s was not registered as a request argument.', 'my-textdomain' ), $param ), array( 'status' => 400 ) );
    }

    // If we got this far then something went wrong don't use user input.
    return new GC_Error( 'rest_api_sad', esc_html__( 'Something went terribly wrong.', 'my-textdomain' ), array( 'status' => 500 ) );
}

在上面的示例中,我们抽象了使用'my-arg'名称。我们可以将这些验证和清理函数用于任何其他参数,这些参数应该是我们指定的结构的字符串。随着代码库和端点的增长,结构将有助于保持代码的轻量级和可维护性。没有结构,您可以验证和清理,但更难跟踪哪些函数应该验证什么。通过将结构添加到请求参数中,我们还可以将参数结构暴露给客户端,以便构建客户端验证库,这可以通过防止无效请求被发送到API来帮助性能。

注意:如果您对使用结构感到不舒服,仍然可以对每个参数进行验证/清理回调,在某些情况下,进行自定义验证最有意义。

摘要

结构在点上可能看起来很愚蠢,可能像不必要的工作一样,但如果您想要可维护、可发现和易于扩展的端点,使用结构至关重要。架构还有助于自动数据人类和计算机的端点!

JSON结构基础知识

GeChiUI实现了一个验证器,该验证器使用JSON结构版本4规范的子集。建议阅读RFC,以更深入地了解JSON结构的工作原理,但本文将描述JSON结构的基础知识以及GeChiUI支持的功能。

API

REST API定义了使用JSON架构的两个主要函数:rest_validate_value_from_schemarest_sanitize_value_from_schema。两个函数都接受请求数据作为第一个参数,接受参数的结构定义作为第二个参数,并可选地接受参数的名称作为第三个参数。验证函数返回trueGC_Error实例,具体取决于数据是否根据结构成功验证。清理函数返回传递给函数的数据的清理形式,如果数据无法安全清理,则返回GC_Error实例。

调用这些函数时,您应该始终注意首先使用rest_validate_value_from_schema验证数据,然后如果该函数返回true,请使用rest_sanitize_value_from_schema对数据进行清理。不同时使用这两个都可以打开您的端点到安全漏洞。

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

如果您的端点没有遵循控制器类结构、从GC_REST_Controller::get_collection_params()返回的args或任何其他未指定回调的实例,GC_REST_Request对象将使用rest_parse_request_arg函数应用清理和验证。重要的是,只有在未定义sanitize_callback时才应用此方法。因此,如果您为参数定义指定自定义sanitize_callback,则内置的JSON结构验证将不适用。如果您需要此验证,您应该在参数定义中手动指定rest_validate_request_argvalidate_callback

架构文档

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

  • $schema对描述文档所用规范版本的元结构的引用。
  • title结构的标题。通常这是一个人类可读标签,但在GeChiUI中,这是一个机器可读字符串。例如,文章端点的标题是“文章”。评论的标题是“评论”。
  • type这指的是所描述的值的类型。这可能是七种原始类型中的任何一种。在GeChiUI中,顶级类型几乎总是一个object,即使是返回对象数组的集合端点。
  • properties对象及其定义中包含的已知属性的列表。每个属性定义本身也是一个结构,但没有$schema顶级属性,则更准确地描述为子结构。

原始类型

JSON结构定义了一个包含七个允许的原始类型的列表。

  • string字符串值。
  • null一个null值。
  • number任何数字。允许小数。相当于PHP中的float
  • integer数字,但不允许使用小数或指数。
  • booleantruefalse值。
  • array值列表。这相当于JavaScript数组。在PHP中,这被称为数字数组,或没有定义键的数组。
  • object值键映射。这相当于JavaScript对象。在PHP中,这被称为关联数组,或具有定义键的数组。

A value’s primitive type is specified using the type keyword. For example, this is how you would define a string value with JSON 结构.


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

JSON结构允许定义可以是多种类型的值。例如,这就是定义stringboolean值的方式。


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

类型杂耍

由于GeChiUI REST API接受POST正文或URL查询部分的URL表单编码数据,许多原始类型执行类型杂耍,将这些字符串值转换为适当的本机类型。

  • string只允许根据is_string
  • null只接受正确键入的null。这意味着无法在URL中提交空值或作为URL表单编码的文章主体提交,因此必须使用JSON请求主体。
  • number允许传递is_numeric的浮点、整数和字符串。值将被转换为float
  • integer可以转换为小数部分等于0float的整数或字符串。
  • boolean布尔值、整数0和1或字符串"0""1""false""true"0被视为false1被视为true
  • array根据gc_is_numeric_array或字符串的数字数组。如果字符串是逗号分隔的,它将被拆分为数组,否则它将是一个包含字符串值的数组。例如"red,yellow"变成aray( "red", "yellow" )"blue"变成array( "blue" )
  • object数组、stdClass对象、实现JsonSerializable的对象或空字符串。值将转换为本机PHP数组。

使用多种类型时,类型将按指定的顺序进行评估。这可能会对REST API端点收到的经过清理的数据产生影响。例如,在前面的示例中,如果提交的值是"1"它将被清理为布尔true值。然而,如果顺序被翻转,该值将保留为字符串"1"

注意:JSON结构规范允许在没有type字段的情况下定义结构。然而,GeChiUI实现需要定义type,如果省略了类型,将发出_doing_it_wrong通知。

格式

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

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


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

GeChiUI支持以下格式:

  • date-time根据RFC3339格式化的日期。
  • uri根据esc_url_rawuri。
  • email根据is_email提供的电子邮件地址。
  • ipv4或v6 ip地址。
  • uuid任何版本的Uuid。
  • hex-color带有前导#3或6个字符的十六进制颜色。

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

例如,以下结构将允许127.0.0.1null作为可能的值。


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

Strings

string类型支持三个额外的关键字。

最小长度和最大长度

minLengthmaxLength关键字可用于限制字符串的可接受长度。重要的是,多字节字符算作单个字符,边界是包容性的。

例如,给定以下结构,ababcabcd有效,而aabcde无效。


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

exclusiveMinimumexclusiveMaximum关键字不适用,它们仅适用于数字。

结构

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

例如,根据以下结构,#123将有效,但#abc无效。


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

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

JSON结构RFC建议将自己限制在以下正则表达式功能上,以便该结构可以在尽可能多的不同编程语言之间互操作。

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

根据ECMA 262正则表达式方言,该结构应该有效。

数字

numberinteger类型支持另外四个关键字。

最小和最大

minimummaximum关键字允许限制可接受的数字范围。例如,根据此结构,2是有效的,但04无效。


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

JSON结构还允许使用exclusiveMinimumexclusiveMaximum关键字来表示该值不能分别等于定义的minimummaximum。例如,在这种情况下,只有2是可接受的值。


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

multipleOf

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


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

multipleOf也支持小数。例如,此结构可用于接受最大小数点后1的百分比。


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

数组

指定arraytype需要数据为数组,但这仅为验证故事的一半。您还需要强制执行数组中每个项目的格式。这是通过指定每个数组项必须使用项关键字符合的JSON结构来实现的。

例如,以下结构需要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和maxItems

minItemsmaxItems关键字可用于限制数组中包含的可接受项数量。

例如,给定以下结构,[ "a" ][ "a", "b" ]有效,而[][ "a", "b", "c" ]无效。


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

exclusiveMinimumexclusiveMaximum关键字不适用,它们仅适用于numberinteger类型。

Unique Items

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

例如,给定以下结构,[ "a", "b" ]有效,而[ "a", "a" ]无效。


array(
    'type'        => 'array',
    'uniqueItems' => true,
    'items'       => array(
        'type' => 'string',
    ),
);
“>独特性”>

不同类型的项目被认为是唯一的,例如"1"11.0是不同的值。

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


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

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


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

rest_validate_value_from_schemarest_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将返回错误。因此,您应该始终验证和清理参数。

对象

指定objecttype需要数据是对象,但这仅是验证故事的一半。您还需要强制执行每个对象属性的格式。这是通过指定对象成员必须使用properties关键字遵守的JSON结构的属性名称映射来实现的。

例如,以下结构需要一个属性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"
}

所需属性

默认情况下,为对象列出的任何属性都是可选的,因此虽然可能出乎意料,但以下内容也会通过对上一个结构的验证。


{
  "name": "Primary"
}

要求提供财产有两种机制。

>版本 3 语法”>

虽然GeChiUI主要遵循JSON结构版本4,但它没有遵循的一种方法是使用语法来定义所需的属性。主要方法是通过在每个属性的定义中添加required关键字来使用JSON架构版本3语法。


array(
    'type'       => 'object',
    'properties' => array(
        'name'  => array(
            'type'     => 'string',
            'required' => true,
        ),
        'color' => array(
            'type'     => 'string',
            'format'   => 'hex-color',
            'required' => true,        
        ),
    ),
);
“>版本4语法”>

GeChiUI还支持JSON结构版本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元字段,则不会产生错误。定义所需属性列表的对象不表示对象本身需要提交。只是,如果包含对象,则列出的属性也必须包含在内。

GC_REST_Controller::get_item_schema()中端点的顶级结构不支持版本4语法。根据给定以下结构,用户可以在没有标题或内容属性的情况下成功提交请求。这是因为结构文档本身不用于验证,而是转换为参数定义列表。


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',
        ),
    ),
);

其他属性

也许不直观的是,默认情况下,JSON结构还允许提供结构中未指定的其他属性。因此,以下内容将通过验证。


{
  "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关键字类似于additionalProperties关键字,但允许断言属性与正则表达式结构匹配。关键字是一个对象,其中每个属性都是正则表达式结构,其值是用于验证与该结构匹配的属性的JSON结构。

例如,此结构要求每个值都是十六进制颜色,属性必须仅包含“单词”字符。

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和maxProperties

minItemsmaxItems关键字可用于array类型。minPropertiesmaxPropertiesobject类型引入了相同的功能。当使用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"
}

exclusiveMinimumexclusiveMaximum关键字不适用,它们仅适用于numberinteger类型。

键入不可知论关键词

oneOf 和 anyOf

这些是高级关键字,允许JSON结构验证器从许多结构中选择一个,以便在验证值时使用。anyOf关键字允许一个值匹配至少一个给定的结构。而oneOf关键字要求值完全匹配一个结构。

例如,此结构允许向端点提交“操作”数组。每个操作都可以是“作物”或“旋转”。

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数组中指定的每个结构,并查找匹配项。如果正好有一个结构匹配,那么验证将成功。如果多个结构匹配,验证将失败。如果没有结构匹配,则验证器将尝试找到最接近的匹配结构并返回适当的错误消息。

操作[0]不是有效的轮岗。原因:操作[0][度数]必须在0(含)到360(含)之间

为了生成更有用的错误消息,强烈建议为每个oneOfanyOf一个title属性。