json: add some non-standard extensions

Also clarify this and previously existing differences to standard JSON
in ipc.rst.
This commit is contained in:
wm4 2018-05-17 16:28:13 +02:00
parent 76bff1a000
commit d36b85cfdf
3 changed files with 77 additions and 7 deletions

View File

@ -74,6 +74,12 @@ some wrapper like .NET's NamedPipeClientStream.)
Protocol
--------
The protocol uses UTF-8-only JSON as defined by RFC-8259. Unlike standard JSON,
"\u" escape sequences are not allowed to construct surrogate pairs. To avoid
getting conflicts, encode all text characters including and above codepoint
U+0020 as UTF-8. mpv might output broken UTF-8 in corner cases (see "UTF-8"
section below).
Clients can execute commands on the player by sending JSON messages of the
following form:
@ -266,4 +272,28 @@ sometimes sends invalid JSON. If that is a problem for the client application's
parser, it should filter the raw data for invalid UTF-8 sequences and perform
the desired replacement, before feeding the data to its JSON parser.
mpv will not attempt to construct invalid UTF-8 with broken escape sequences.
mpv will not attempt to construct invalid UTF-8 with broken "\u" escape
sequences. This includes surrogate pairs.
JSON extensions
---------------
The following non-standard extensions are supported:
- a list or object item can have a trailing ","
- object syntax accepts "=" in addition of ":"
- object keys can be unquoted, if they start with a character in "A-Za-z\_"
and contain only characters in "A-Za-z0-9\_"
- byte escapes with "\xAB" are allowed (with AB being a 2 digit hex number)
Example:
::
{ objkey = "value\x0A" }
Is equivalent to:
::
{ "objkey": "value\n" }

View File

@ -22,7 +22,12 @@
* doesn't verify what's passed to strtod(), and also prefers parsing numbers
* as integers with stroll() if possible).
*
* Does not support extensions like unquoted string literals.
* It has some non-standard extensions which shouldn't conflict with JSON:
* - a list or object item can have a trailing ","
* - object syntax accepts "=" in addition of ":"
* - object keys can be unquoted, if they start with a character in [A-Za-z_]
* and contain only characters in [A-Za-z0-9_]
* - byte escapes with "\xAB" are allowed (with AB being a 2 digit hex number)
*
* Also see: http://tools.ietf.org/html/rfc8259
*
@ -45,6 +50,7 @@
#include "common/common.h"
#include "misc/bstr.h"
#include "misc/ctype.h"
#include "json.h"
@ -72,6 +78,24 @@ void json_skip_whitespace(char **src)
eat_ws(src);
}
static int read_id(void *ta_parent, struct mpv_node *dst, char **src)
{
char *start = *src;
if (!mp_isalpha(**src) && **src != '_')
return -1;
while (mp_isalnum(**src) || **src == '_')
*src += 1;
if (**src == ' ') {
**src = '\0'; // we're allowed to mutate it => can avoid the strndup
*src += 1;
} else {
start = talloc_strndup(ta_parent, start, *src - start);
}
dst->format = MPV_FORMAT_STRING;
dst->u.string = start;
return 0;
}
static int read_str(void *ta_parent, struct mpv_node *dst, char **src)
{
if (!eat_c(src, '"'))
@ -122,12 +146,18 @@ static int read_sub(void *ta_parent, struct mpv_node *dst, char **src,
if (list->num > 0 && !eat_c(src, ','))
return -1; // missing ','
eat_ws(src);
// non-standard extension: allow a trailing ","
if (eat_c(src, term))
break;
if (is_obj) {
struct mpv_node keynode;
if (read_str(list, &keynode, src) < 0)
// non-standard extension: allow unquoted strings as keys
if (read_id(list, &keynode, src) < 0 &&
read_str(list, &keynode, src) < 0)
return -1; // key is not a string
eat_ws(src);
if (!eat_c(src, ':'))
// non-standard extension: allow "=" instead of ":"
if (!eat_c(src, ':') && !eat_c(src, '='))
return -1; // ':' missing
eat_ws(src);
MP_TARRAY_GROW(list, list->keys, list->num);

View File

@ -45,14 +45,24 @@ static const struct entry entries[] = {
{ "[1,2,3]", "[1,2,3]",
NODE_ARRAY(NODE_INT64(1), NODE_INT64(2), NODE_INT64(3))},
{ "[ ]", "[]", NODE_ARRAY()},
{ "[1,2,]", .expect_fail = true},
{ "[1,,2]", .expect_fail = true},
{ "[,]", .expect_fail = true},
{ TEXT({"a":1, "b":2}), TEXT({"a":1,"b":2}),
NODE_MAP(L("a", "b"), L(NODE_INT64(1), NODE_INT64(2)))},
{ "{ }", "{}", NODE_MAP(L(), L())},
{ TEXT({"a":b}), .expect_fail = true},
{ TEXT({a:"b"}), .expect_fail = true},
{ TEXT({"a":1,}), .expect_fail = true},
{ TEXT({1a:"b"}), .expect_fail = true},
// non-standard extensions
{ "[1,2,]", "[1,2]", NODE_ARRAY(NODE_INT64(1), NODE_INT64(2))},
{ TEXT({a:"b"}), TEXT({"a":"b"}),
NODE_MAP(L("a"), L(NODE_STR("b")))},
{ TEXT({a="b"}), TEXT({"a":"b"}),
NODE_MAP(L("a"), L(NODE_STR("b")))},
{ TEXT({a ="b"}), TEXT({"a":"b"}),
NODE_MAP(L("a"), L(NODE_STR("b")))},
{ TEXT({_a12="b"}), TEXT({"_a12":"b"}),
NODE_MAP(L("_a12"), L(NODE_STR("b")))},
};
#define MAX_DEPTH 10