Commit f4eb3a18 authored by Joey Adams's avatar Joey Adams Committed by Rusty Russell

json: new module for parsing and generating JSON

parent 02844236
../../licenses/BSD-MIT
\ No newline at end of file
#include <string.h>
#include "config.h"
/**
* json - Parse and generate JSON (JavaScript Object Notation)
*
* This is a library for encoding and decoding JSON that strives to be
* easy to learn, use, and incorporate into an application.
*
* JSON (JavaScript Object Notation) facilitates passing data among different
* programming languages, particularly JavaScript. It looks like this:
*
* [
* {
* "id": 1,
* "firstname": "John",
* "lastname": "Smith",
* "email": "john@example.com",
* "likes_pizza": false
* },
* {
* "id": 2,
* "firstname": "Linda",
* "lastname": "Jones",
* "email": null,
* "likes_pizza": true
* }
* ]
*
* Example:
* #include <ccan/json/json.h>
* #include <math.h>
* #include <stdio.h>
* #include <stdlib.h>
*
* static int find_number(JsonNode *object, const char *name, double *out)
* {
* JsonNode *node = json_find_member(object, name);
* if (node && node->tag == JSON_NUMBER) {
* *out = node->number_;
* return 1;
* }
* return 0;
* }
*
* static void solve_pythagorean(JsonNode *triple)
* {
* double a = 0, b = 0, c = 0;
* int a_given, b_given, c_given;
*
* if (triple->tag != JSON_OBJECT) {
* fprintf(stderr, "Error: Expected a JSON object.\n");
* exit(EXIT_FAILURE);
* }
*
* a_given = find_number(triple, "a", &a);
* b_given = find_number(triple, "b", &b);
* c_given = find_number(triple, "c", &c);
*
* if (a_given + b_given + c_given != 2) {
* fprintf(stderr, "Error: I need two sides to compute the length of the third.\n");
* exit(EXIT_FAILURE);
* }
*
* if (a_given && b_given) {
* c = sqrt(a*a + b*b);
* json_append_member(triple, "c", json_mknumber(c));
* } else if (a_given && c_given) {
* b = sqrt(c*c - a*a);
* json_append_member(triple, "b", json_mknumber(b));
* } else if (b_given && c_given) {
* a = sqrt(c*c - b*b);
* json_append_member(triple, "a", json_mknumber(a));
* }
* }
*
* int main(void)
* {
* JsonNode *triples = json_mkarray();
*
* json_append_element(triples, json_decode("{\"a\": 3, \"b\": 4}"));
* json_append_element(triples, json_decode("{\"a\": 5, \"c\": 13}"));
* json_append_element(triples, json_decode("{\"b\": 24, \"c\": 25}"));
*
* JsonNode *triple;
* json_foreach(triple, triples)
* solve_pythagorean(triple);
*
* char *tmp = json_stringify(triples, "\t");
* puts(tmp);
* free(tmp);
*
* json_delete(triples);
* return 0;
* }
*
* Author: Joey Adams
* Version: 0.1
* License: MIT
*/
int main(int argc, char *argv[])
{
/* Expect exactly one argument */
if (argc != 2)
return 1;
if (strcmp(argv[1], "depends") == 0) {
/* Nothing */
return 0;
}
if (strcmp(argv[1], "libs") == 0) {
printf("m\n"); /* Needed for sqrt() used in example code above. */
return 0;
}
return 1;
}
This diff is collapsed.
/*
Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com)
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef CCAN_JSON_H
#define CCAN_JSON_H
#include <stdbool.h>
#include <stddef.h>
typedef enum {
JSON_NULL,
JSON_BOOL,
JSON_STRING,
JSON_NUMBER,
JSON_ARRAY,
JSON_OBJECT,
} JsonTag;
typedef struct JsonNode JsonNode;
struct JsonNode
{
/* only if parent is an object or array (NULL otherwise) */
JsonNode *parent;
JsonNode *prev, *next;
/* only if parent is an object (NULL otherwise) */
char *key; /* Must be valid UTF-8. */
JsonTag tag;
union {
/* JSON_BOOL */
bool bool_;
/* JSON_STRING */
char *string_; /* Must be valid UTF-8. */
/* JSON_NUMBER */
double number_;
/* JSON_ARRAY */
/* JSON_OBJECT */
struct {
JsonNode *head, *tail;
} children;
};
};
/*** Encoding, decoding, and validation ***/
JsonNode *json_decode (const char *json);
char *json_encode (const JsonNode *node);
char *json_encode_string (const char *str);
char *json_stringify (const JsonNode *node, const char *space);
void json_delete (JsonNode *node);
bool json_validate (const char *json);
/*** Lookup and traversal ***/
JsonNode *json_find_element (JsonNode *array, int index);
JsonNode *json_find_member (JsonNode *object, const char *key);
JsonNode *json_first_child (const JsonNode *node);
#define json_foreach(i, object_or_array) \
for ((i) = json_first_child(object_or_array); \
(i) != NULL; \
(i) = (i)->next)
/*** Construction and manipulation ***/
JsonNode *json_mknull(void);
JsonNode *json_mkbool(bool b);
JsonNode *json_mkstring(const char *s);
JsonNode *json_mknumber(double n);
JsonNode *json_mkarray(void);
JsonNode *json_mkobject(void);
void json_append_element(JsonNode *array, JsonNode *element);
void json_prepend_element(JsonNode *array, JsonNode *element);
void json_append_member(JsonNode *object, const char *key, JsonNode *value);
void json_prepend_member(JsonNode *object, const char *key, JsonNode *value);
void json_remove_from_parent(JsonNode *node);
/*** Debugging ***/
/*
* Look for structure and encoding problems in a JsonNode or its descendents.
*
* If a problem is detected, return false, writing a description of the problem
* to errmsg (unless errmsg is NULL).
*/
bool json_check(const JsonNode *node, char errmsg[256]);
#endif
A JSON fragment (rather than a "text", which may only be an object or array) has the following properties:
* The length is >= 1 (don't forget about single-digit numbers).
* The first character is ASCII.
* The last character is ASCII.
[RFC4627] assumes a "JSON text", where the first two characters will always be ASCII. Encoding detection for a JSON fragment requires doing the proof all over again, due to the fact that the second character might not be ASCII (e.g. "Česká republika").
Let's start:
If the first byte is null, then it's UTF-32BE or UTF-16BE. Otherwise, it's UTF-32LE, UTF-16LE, or UTF-8.
But what if the text starts with a BOM? Then the first byte will be non-NULL for UTF-16BE. If a BOM is detected, we can go by that, making our lives "easier".
int json_detect_encoding(const unsigned char *s, const unsigned char *e)
{
if (s >= e)
return JSONENC_INVALID;
}
<strike>Assumption: the first character in the Unicode string is an non-null ASCII character, and the string is at least one character long.</strike>
If s[0..2] == 0, then 0 < s[3] <= 0x7F, or the string is invalid.
For any valid UTF16LE without BOM:
s[0] is ASCII, and s[1] is 0.
For any valid UTF16BE without BOM:
s[0] is 0, and s[1] is ASCII.
For any valid UTF32LE without BOM:
s[0] is ASCII, and s[1..3] is 0.
I think with the assumption above, there's an ambiguity between UTF16LE and UTF32LE:
7x 00 00 00
This can be any of the following:
* UTF-8, with an ASCII character and 3 null characters.
* UTF-16LE, with an ASCII character and a null character.
* UTF-32LE, with an ASCII character and nothing else.
Therefore, I will extend it.
Assumption: The string is not empty, contains no null characters, and the first character is in the ASCII range.
For any valid UTF8 without BOM:
s[0] is non-null ASCII, and, if e - s > 1, s[1] is not 0.
For any valid UTF16BE without BOM:
s[0] is 0, s[1] is non-null ASCII, and, if e - s >= 4, one or both of s[2,3] are non-zero.
For any valid UTF16LE without BOM:
s[0] is non-null ASCII, s[1] is 0, and, if e - s >= 4, one or both of s[2,3] are non-zero.
For any valid UTF32BE without BOM:
s[0..2] is 0, and s[3] is ASCII.
For any valid UTF32LE without BOM:
s[0] is ASCII, and s[1..3] is 0.
For any valid UTF8 with BOM:
s[0..2] is {0xEF, 0xBB, 0xBF}.
For any valid UTF16BE with BOM:
s[0] is 0xFE, and s[1] is 0xFF.
For any valid UTF16LE with BOM:
s[0] is 0xFF, s[1] is 0xFE, and s[2] is non-null ASCII.
For any valid UTF32BE with BOM:
s[0] is 0, s[1] is 0, s[2] is 0xFE, and s[3] is 0xFF.
For any valid UTF32LE with BOM:
s[0] is 0xFF, s[1] is 0xFE, s[2] is 0, and s[3] is 0.
Condensed version (for any valid string in the given encoding and with the assumption above, the beginning of the bytes will always match the pattern):
Without BOM:
UTF8 7x (xx | $)
UTF16BE 00 7x
UTF16LE 7x 00 (00 xx | xx 00 | xx xx | $)
UTF32BE 00 00 00 7x
UTF32LE 7x 00 00 00
With BOM:
UTF8 EF BB BF
UTF16BE FE FF
UTF16LE FF FE 7x
UTF32BE 00 00 FE FF
UTF32LE FF FE 00 00
Key:
00 Null byte
xx Non-null byte
7x Non-null ASCII byte
$ End of byte string
As fun as this is, I've decided not to worry about encoding conversion for now, and assume input is valid UTF-8.
An issue more likely to affect users is expecting to be able to load binary into a JSON string, given the current API. Thus, I plan to introduce a restriction to JSON not present in the RFC, but present in the de-facto standard (i.e. IE): JSON strings (keys and values) may not contain null characters. "\u0000" will be treated as invalid.
Maybe I should just make a JSON serialization library. On the other hand, the hard, JSON-specific bits (encoding/decoding strings, validating numbers, etc.) should be available, and having a "json" library that does it is helpful.
My plan, then, is for the JSON module to house two things:
* A set of parsing and emitting primitives that take care of the hard parts of JSON.
* A simple parser and printer, geared toward usefulness over precision.
Change of plans: my goal is to have a JSON library that's SIMPLE. I looked at some of the implementations in C and C++, and they're obsessed with iterators, streams, hashes, manipulation, etc. (Even I'm going down that road with my iobuffer stuff). They're often hard to integrate into projects, since they consist of several translation units and expect to be built like the huge libraries that they shouldn't be.
Unicode functions are going to be incorporated directly into the module whether you like it or not.
"parse" and "emit" functions will be private. Steal them from the source code if you want them.
I plan to take a lenient approach to Unicode: invalid characters are converted to replacement characters rather than producing flat-out failures. I don't want everything to grind to a halt because Jöšé Hérnàñdعz signed up, but some client (examples: Internet Explorer, and the machines) decided not to produce valid UTF-8.
The purpose of JSON is to facilitate communication among programming languages. If a programming language cannot handle part of the spec idiomatically, it shouldn't. This justifies using C strings instead of pointer/length pairs. It also justifies only supporting ASCII instead of Unicode (e.g. via UTF-8, surrogate pairs, etc.), but there is a clear, practical reasons to support Unicode: people speak different languages, and most people speak languages containing non-ASCII characters.
It's a lot easier to just validate UTF-8 rather than tolerate it by replacement. However, I might as well replace invalid surrogate pairs.
Now it's time to come up with a description for my JSON API. The most important thing about it is that it's SIMPLE. I should also mention the "purpose of JSON" paragraph to introduce JSON as "a simple text-based format that facilitates transferring information between programming languages" or similar.
Another thing to document is that this library supports JSON values: it does not enforce the draconian restriction that the toplevel be an object or an array.
For the sake of the simplicity I love, I'll require valid UTF-8 and require valid surrogate pairs. Also, I'll remove the dependency on charset.
Favors ease of use over losslessness:
* C strings: Although JSON allows \u0000 escapes (if I'm not mistaken), they don't always work right in some browsers.
* double: This may seem clumsy, but double can store 32-bit integers losslessly, and the numbers are printed with enough decimal places that 32-bit integers won't be truncated.
Does not include comprehensive facilities for manipulating JSON structures. Instead, it tries to get out of your way so you can serialize and unserialize as you see fit.
* Uses a linked list instead of a mapping.
The code is currently a little ugly as far as toplevel organization goes.
Things to test:
* List link pointers are consistent.
* 32-bit signed and unsigned integers are preserved verbatim.
* Appending, prepending, and and looking up members works.
* Appending and prepending items works.
* Valid and invalid UTF-8 in JSON input is handled properly.
* json_decode returns NULL for invalid strings.
* json_encode_string works
* json_stringify works with a non-NULL space argument
* Lookup functions return NULL when given NULL or invalid inputs.
* Removing the first, last, or only child of a node works properly.
* Bogus literals starting with 'n', 'f', 't' parse as invalid (e.g. 'nil', 'fals', 'falsify', 'falsetto', and 'truism').
* Key without colon or value.
* All escapes are parsed and unparsed.
* \u0000 is disallowed.
* 0.0 / 0.0 converts to null in JSON.
Ways to test these:
* json_decode every test string.
* Add test strings for:
- Bogus literals
- Keys without colon or value
- \u0000
* Manually test escape parsing/unparsing, with some salt around the edges, too.
* Expose escaping unicode, and test that with the test strings.
* Build a list of numbers with various appends and prepends, verify them by testing against their encoded value, do pointer consistency checks each time, do element lookups, and remove items as well.
* Write tests for stringify.
* Test various ranges of 32-bit signed and unsigned integers, converting them to and from JSON and ensuring that the value was preserved.
Hmm, I wonder if Unicode escaping should be a separate function.
I implemented some of the above. Things still not covered by tests:
* Out-of-memory situations
* Invalid UTF-8
* Non-ASCII characters in input
* Unicode characters from U+0080..U+07FF
* Escaping Unicode characters (not even exposed by the API)
* json_encode_string
* Parsing \f
* Emitting string values in json_stringify with non-NULL space.
* Passing invalid nodes to json_check
#include <ccan/json/json.c>
#include <ccan/tap/tap.h>
#include <errno.h>
#include <string.h>
static char *chomp(char *s)
{
char *e;
if (s == NULL || *s == 0)
return s;
e = strchr(s, 0);
if (e[-1] == '\n')
*--e = 0;
return s;
}
/* Build a list of numbers with various appends and prepends, verify them by testing against their encoded value, do pointer consistency checks each time, do element lookups, and remove items as well. */
#include "common.h"
#define should_be(var, expected) should_be_(var, #var, expected)
static void should_be_(const JsonNode *node, const char *name, const char *expected)
{
char errmsg[256];
char *encoded;
if (!json_check(node, errmsg)) {
fail("Invariants check failed: %s", errmsg);
return;
}
encoded = json_encode(node);
if (strcmp(encoded, expected) == 0)
pass("%s is %s", name, expected);
else
fail("%s should be %s, but is actually %s", name, expected, encoded);
free(encoded);
}
static void test_string(void)
{
JsonNode *str;
str = json_mkstring("Hello\tworld!\n\001");
should_be(str, "\"Hello\\tworld!\\n\\u0001\"");
json_delete(str);
str = json_mkstring("\"\\\b\f\n\r\t");
should_be(str, "\"\\\"\\\\\\b\\f\\n\\r\\t\"");
json_delete(str);
}
static void test_number(void)
{
JsonNode *num;
num = json_mknumber(5678901234.0);
should_be(num, "5678901234");
json_delete(num);
num = json_mknumber(-5678901234.0);
should_be(num, "-5678901234");
json_delete(num);
num = json_mknumber(0.0 / 0.0);
should_be(num, "null");
json_delete(num);
}
static void test_array(void)
{
JsonNode *array;
JsonNode *children[5 + 1];
array = json_mkarray();
should_be(array, "[]");
children[1] = json_mknumber(1);
children[2] = json_mknumber(2);
children[3] = json_mknumber(3);
children[4] = json_mknumber(4);
children[5] = json_mknumber(5);
json_append_element(array, children[3]);
should_be(array, "[3]");
json_remove_from_parent(children[3]);
should_be(array, "[]");
json_prepend_element(array, children[3]);
should_be(array, "[3]");
json_prepend_element(array, children[2]);
should_be(array, "[2,3]");
json_append_element(array, children[4]);
should_be(array, "[2,3,4]");
json_delete(children[3]);
should_be(array, "[2,4]");
json_prepend_element(array, children[1]);
should_be(array, "[1,2,4]");
json_delete(children[1]);
should_be(array, "[2,4]");
json_delete(children[4]);
should_be(array, "[2]");
ok1(json_find_element(array, 0) == children[2]);
ok1(json_find_element(array, -1) == NULL);
ok1(json_find_element(array, 1) == NULL);
json_append_element(array, children[5]);
should_be(array, "[2,5]");
ok1(json_find_element(array, 0) == children[2]);
ok1(json_find_element(array, 1) == children[5]);
ok1(json_find_element(array, -1) == NULL);
ok1(json_find_element(array, 2) == NULL);
json_delete(children[2]);
json_delete(children[5]);
should_be(array, "[]");
ok1(json_find_element(array, -1) == NULL);
ok1(json_find_element(array, 0) == NULL);
ok1(json_find_element(array, 1) == NULL);
json_delete(array);
}
static void test_object(void)
{
JsonNode *object;
JsonNode *children[5 + 1];
object = json_mkobject();
should_be(object, "{}");
children[1] = json_mknumber(1);
children[2] = json_mknumber(2);
children[3] = json_mknumber(3);
ok1(json_find_member(object, "one") == NULL);
ok1(json_find_member(object, "two") == NULL);
ok1(json_find_member(object, "three") == NULL);
json_append_member(object, "one", children[1]);
should_be(object, "{\"one\":1}");
ok1(json_find_member(object, "one") == children[1]);
ok1(json_find_member(object, "two") == NULL);
ok1(json_find_member(object, "three") == NULL);
json_prepend_member(object, "two", children[2]);
should_be(object, "{\"two\":2,\"one\":1}");
ok1(json_find_member(object, "one") == children[1]);
ok1(json_find_member(object, "two") == children[2]);
ok1(json_find_member(object, "three") == NULL);
json_append_member(object, "three", children[3]);
should_be(object, "{\"two\":2,\"one\":1,\"three\":3}");
ok1(json_find_member(object, "one") == children[1]);
ok1(json_find_member(object, "two") == children[2]);
ok1(json_find_member(object, "three") == children[3]);
json_delete(object);
}
int main(void)
{
JsonNode *node;
(void) chomp;
plan_tests(49);
ok1(json_find_element(NULL, 0) == NULL);
ok1(json_find_member(NULL, "") == NULL);
ok1(json_first_child(NULL) == NULL);
node = json_mknull();
should_be(node, "null");
json_delete(node);
node = json_mkbool(false);
should_be(node, "false");
json_delete(node);
node = json_mkbool(true);
should_be(node, "true");
json_delete(node);
test_string();
test_number();
test_array();
test_object();
return exit_status();
}
#include "common.h"
int main(void)
{
const char *strings_file = "test/test-strings";
const char *strings_reencoded_file = "test/test-strings-reencoded";
FILE *f, *f2;
char buffer[1024], buffer2[1024];
plan_tests(90);
f = fopen(strings_file, "rb");
if (f == NULL) {
diag("Could not open %s: %s", strings_file, strerror(errno));
return 1;
}
f2 = fopen(strings_reencoded_file, "rb");
if (f2 == NULL) {
diag("Could not open %s: %s", strings_reencoded_file, strerror(errno));
return 1;
}
while (fgets(buffer, sizeof(buffer), f)) {
const char *s = chomp(buffer);
bool valid;
JsonNode *node;
if (expect_literal(&s, "valid ")) {
valid = true;
} else if (expect_literal(&s, "invalid ")) {
valid = false;
} else {
fail("Invalid line in test-strings: %s", buffer);
continue;
}
node = json_decode(s);
if (valid) {
char *reencoded;
char errmsg[256];
if (node == NULL) {
fail("%s is valid, but json_decode returned NULL", s);
continue;
}
if (!json_check(node, errmsg)) {
fail("Corrupt tree produced by json_decode: %s", errmsg);
continue;
}
reencoded = json_encode(node);
if (!fgets(buffer2, sizeof(buffer2), f2)) {
fail("test-strings-reencoded is missing this line: %s", reencoded);
continue;
}
chomp(buffer2);
ok(strcmp(reencoded, buffer2) == 0, "re-encode %s -> %s", s, reencoded);
free(reencoded);
json_delete(node);
} else if (node != NULL) {
fail("%s is invalid, but json_decode returned non-NULL", s);
continue;
}
}
if (ferror(f) || fclose(f) != 0 || ferror(f2) || fclose(f2) != 0) {
diag("I/O error reading test data.");
return 1;
}
return exit_status();
}
#include "common.h"
static char buf1[256], buf2[256];
/* Used for pass and fail messages */
static char *quote_string(const char *str, char buf[256])
{
char *out = buf;
*out++ = '"';
for (; *str != 0; str++) {
if (out - buf > 256 - 5) {
/* String is too long. End it with `...' */
out = buf + 256 - 5;
*out++ = '.';
*out++ = '.';
*out++ = '.';
break;
}
switch (*str) {
case '\t':
*out++ = '\\';
*out++ = 't';
break;
case '\n':
*out++ = '\\';
*out++ = 'n';
break;
case '"':
*out++ = '\\';
*out++ = '"';
break;
case '\\':
*out++ = '\\';
*out++ = '\\';
break;
default:
*out++ = *str;
break;
}
}
*out++ = '"';
*out = 0;
return buf;
}
static void test_stringify(const char *input, const char *expected)
{
JsonNode *node = NULL;
char *enc = NULL;
char *strn = NULL;
char *str = NULL;
node = json_decode(input);
if (node == NULL) {
fail("Failed to decode %s", input);
goto end;
}
enc = json_encode(node);
if (strcmp(enc, input) != 0) {
fail("%s re-encodes to %s. Either encode/decode is broken, or the input string needs to be normalized", input, enc);
goto end;
}
strn = json_stringify(node, NULL);
if (strcmp(strn, enc) != 0) {
fail("json_stringify with NULL space produced a different string than json_encode");
goto end;
}
str = json_stringify(node, "\t");
if (strcmp(str, expected) != 0) {
fail("Expected %s, but json_stringify produced %s",
quote_string(expected, buf1), quote_string(str, buf2));
goto end;
}
pass("stringify %s", input);
end:
json_delete(node);
free(enc);
free(strn);
free(str);
}
int main(void)
{
(void) chomp;
plan_tests(9);
test_stringify("[]", "[]");
test_stringify("[1]", "[\n\t1\n]");
test_stringify("[1,2,3]", "[\n\t1,\n\t2,\n\t3\n]");
test_stringify("[[]]", "[\n\t[]\n]");
test_stringify("[[1,2],[3,4]]", "[\n\t[\n\t\t1,\n\t\t2\n\t],\n\t[\n\t\t3,\n\t\t4\n\t]\n]");
test_stringify("{}", "{}");
test_stringify("{\"one\":1}", "{\n\t\"one\": 1\n}");
test_stringify("{\"one\":1,\"t*\":[2,3,10]}", "{\n\t\"one\": 1,\n\t\"t*\": [\n\t\t2,\n\t\t3,\n\t\t10\n\t]\n}");
test_stringify("{\"a\":{\"1\":1,\"2\":2},\"b\":{\"3\":[null,false,true,\"\\f\"]}}",
"{\n\t\"a\": {\n\t\t\"1\": 1,\n\t\t\"2\": 2\n\t},\n\t\"b\": {\n\t\t\"3\": [\n\t\t\tnull,\n\t\t\tfalse,\n\t\t\ttrue,\n\t\t\t\"\\f\"\n\t\t]\n\t}\n}");
return exit_status();
}
#include "common.h"
int main(void)
{
const char *strings_file = "test/test-strings";
FILE *f;
char buffer[1024];
plan_tests(224);
f = fopen(strings_file, "rb");
if (f == NULL) {
diag("Could not open %s: %s", strings_file, strerror(errno));
return 1;
}
while (fgets(buffer, sizeof(buffer), f)) {
const char *s = chomp(buffer);
bool valid;
if (expect_literal(&s, "valid ")) {
valid = true;
} else if (expect_literal(&s, "invalid ")) {
valid = false;
} else {
fail("Invalid line in test-strings: %s", buffer);
continue;
}
if (strcmp(s, "\"1\\u2\"") == 0)
puts("here");
if (json_validate(s) == valid) {
pass("%s %s", valid ? "valid" : "invalid", s);
} else {
fail("%s is %s, but json_validate returned %s",
s,
valid ? "valid" : "invalid",
valid ? "false" : "true");
}
}
if (ferror(f) || fclose(f) != 0) {
diag("I/O error reading test strings.");
return 1;
}
return exit_status();
}
invalid
invalid
invalid "
invalid [,]
invalid [)
invalid []]
invalid [}
invalid {,}
invalid {]
invalid ["1":2]
invalid [1,2,]
invalid [1:2}
invalid {"1":2,}
invalid {1:2}
invalid {"1":2, "2.5" : [3, 4, {}, {"5": ["6"], [7 ]}]}
invalid {"1":2, "2.5" : [3, 4, {}, {"5": ["6"], [7]}]}
invalid {"1":2, "2.5" : [3, 4, {}, {"5": ["6"], "7" :[8 ]}]
invalid {"1":2, "2.5" : [3, 4, {}, {"5": ["6"], "7" :[8 ]}]]
invalid {"1":2, "3":4
invalid "1\u2"
invalid [,2]
invalid "3
invalid "3" "4"
invalid [3[4]
invalid [3[4]]
invalid [3, [4, [5], 6] 7, 8 9]
invalid [3, [4, [5], 6] 7, 8, 9]
invalid [3, [4, [5], 6], 7, 8 9]
invalid {"hello":true, "bye":false, null}
invalid {"hello":true, "bye":false, null:null}
invalid "hi
invalid "hi"""
invalid {"hi": "bye"]
invalid "\uD800\uD800"
invalid "\uD800\uDBFF"
invalid "\UD834\UDD1E"
invalid "\uDB00"
invalid "\uDB00\uDBFF"
valid "\uFFFE"
valid "\uFFFF"
invalid .
valid ""
valid []
valid {}
invalid +.
valid 0.5
invalid 0.e1
valid {"1":{}}
valid {"1":2}
valid {"1":2, "2.5" : [3, 4, {}, {"5": ["6"]}]}
valid {"1":2, "2.5" : [3, 4, {}, {"5": ["6"], "7" :[8 ]}]}
valid 1234
valid -1234
valid {"1":2, "3":4}
invalid +1234
invalid ++1234
valid 123.456e142
valid 123.456e-142
valid 123.456e+142
invalid 123.e-142
valid "1\u2000"
valid "1\u20001"
valid 2
invalid .246e-142
invalid .2e-142
valid 3
invalid .3
valid "3"
valid [3]
invalid +3.
valid 3.2e+1
valid [3, [4]]
valid [3, [4, [5]]]
valid [3, [4, [5], 6]]
valid [3, [4, [5], 6], 7]
valid [3, [4, [5], 6], 7, 8]
valid [3, [4, [5], 6], 7, 8, 9]
invalid +3.5
invalid .3e
invalid .3e1
invalid .3e-1
invalid .3e+1
invalid 3.e1
invalid 3.e+1
valid 3e+1
invalid .5
invalid +.5
invalid .5e+1
valid [ 7]
valid [7 ]
valid [7]
invalid .e-14234
valid "hello"
valid ["hello"]
valid ["hello", "bye"]
valid ["hello", "bye\n"]
valid ["hello", "bye\n\r\t"]
valid ["hello", "bye\n\r\t\b"]
valid ["hello", "bye\n\r\t\b",true]
valid ["hello", "bye\n\r\t\b",true , false]
valid ["hello", "bye\n\r\t\b",true , false, null]
invalid ["hello", "bye\n\r\t\v"]
valid {"hello":true}
valid {"hello":true, "bye":false}
valid {"hello":true, "bye":false, "foo":["one","two","three"]}
valid "hi"
valid ["hi"]
valid ["hi", "bye"]
valid {"hi": "bye"}
valid ["hi", "bye", 3]
valid ["hi", "bye[", 3]
valid "\u0007"
valid "\u0008"
valid "\u0009"
valid "\u0010"
valid "\u0020"
valid "\u10000"
valid "\u1234"
valid "\u99999"
valid "\ud800\udc00"
valid "\uD800\uDC00"
valid "\uD834\uDD1E"
valid "\uDBFF\uDFFF"
valid "\uFFFD"
valid "\uFFFF"
invalid hello
valid [32, 1]
invalid [32,
valid "\uD800\uDC00"
valid "\n"
valid "hello"
valid "hello\u0009world"
valid "hello"
valid "hello\n"
valid "hello"
valid 3
invalid 3.
invalid .3
valid 0.3
invalid 0.3e
invalid 0.3e+
valid 0.3e+5
valid 0.3e-5
valid 0.3e5
valid "hello"
invalid +3
valid -3
invalid -3.
valid -3.1
invalid .5
invalid 5.
invalid 5.e1
valid 0.5
invalid .3e1
invalid .3e+1
invalid .3e-1
invalid .3e-1 .5
invalid .3e-1.5
invalid .3e+1.5
invalid .3e+.
invalid .3e+.5
invalid .3e+1.5
invalid 9.3e+1.5
invalid 9.e+1.5
invalid 9.e+
invalid 9.e+1
valid "\""
valid "\"3.5"
valid "\"."
invalid "\".".
valid "\"....."
invalid "\"\"\"\"""
invalid ["\"\"\"\"", .5]
invalid [.5]
valid ["\"\"\"\"", 0.5]
invalid ["\"\"\"\"", .5]
invalid ["\"\"\"\"",.5]
invalid ["\"",.5]
invalid ["\".5",.5]
invalid ["\".5",".5\"".5]
invalid ["\".5",".5\"", .5]
invalid ["\".5",".5\"",.5]
valid ["\".5",".5\"",0.5]
invalid {"key":/*comment*/"value"}
invalid {"key":/*comment"value"}
invalid {"key":"value"}/*
invalid {"key":"value"}/**/
invalid {"key":"value"}/***/
invalid {"key":"value"}/**//
invalid {"key":"value"}/**///
invalid {"key":"value"}/**///----
invalid {"key":"value"}#
invalid {"key":"value"}#{
invalid {"key":"value"}#{}
invalid {"key":"value"}#,
invalid {"key":"value"/**/, "k2":"v2"}
valid "\u0027"
invalid "hello\'"
invalid 'hello\''
invalid 'hello'
invalid 'hell\'o'
invalid '\'hello'
invalid '\'hello\''
invalid \'hello\'
invalid 'hello\'
invalid ['hello\']
invalid ['hello\'']
invalid ['hello"']
invalid ['hello\"']
invalid ['hello"o']
invalid ['"']
invalid '"'
invalid '"hello"'
invalid '"hello'
invalid '"hi"'
valid [ 1 , 2 , 3 ]
invalid nil
invalid fals
invalid falsify
invalid falsetto
invalid truism
invalid {"key"
invalid {"key","key2":value}
invalid "\u0000"
"￾"
"￿"
""
[]
{}
0.5
{"1":{}}
{"1":2}
{"1":2,"2.5":[3,4,{},{"5":["6"]}]}
{"1":2,"2.5":[3,4,{},{"5":["6"],"7":[8]}]}
1234
-1234
{"1":2,"3":4}
1.23456e+144
1.23456e-140
1.23456e+144
"1 "
"1 1"
2
3
"3"
[3]
32
[3,[4]]
[3,[4,[5]]]
[3,[4,[5],6]]
[3,[4,[5],6],7]
[3,[4,[5],6],7,8]
[3,[4,[5],6],7,8,9]
30
[7]
[7]
[7]
"hello"
["hello"]
["hello","bye"]
["hello","bye\n"]
["hello","bye\n\r\t"]
["hello","bye\n\r\t\b"]
["hello","bye\n\r\t\b",true]
["hello","bye\n\r\t\b",true,false]
["hello","bye\n\r\t\b",true,false,null]
{"hello":true}
{"hello":true,"bye":false}
{"hello":true,"bye":false,"foo":["one","two","three"]}
"hi"
["hi"]
["hi","bye"]
{"hi":"bye"}
["hi","bye",3]
["hi","bye[",3]
"\u0007"
"\b"
"\t"
"\u0010"
" "
"က0"
"ሴ"
"香9"
"𐀀"
"𐀀"
"𝄞"
"􏿿"
"�"
"￿"
[32,1]
"𐀀"
"\n"
"hello"
"hello\tworld"
"hello"
"hello\n"
"hello"
3
0.3
30000
3e-06
30000
"hello"
-3
-3.1
0.5
"\""
"\"3.5"
"\"."
"\"....."
["\"\"\"\"",0.5]
["\".5",".5\"",0.5]
"'"
[1,2,3]
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment