#endif
/***
- * === FIXME
+ * === JSON library context
+ *
+ * FIXME: Document memory management
***/
+/**
+ * The context is represented a pointer to this structure.
+ * The fields marked with [*] are publicly accessible, the rest is private.
+ **/
struct json_context {
+ // Memory management
struct mempool *pool;
struct mempool_state init_state;
+ // Parser context
struct fastbuf *in_fb;
- uint in_line;
- uint in_column;
- bool in_eof;
+ uint in_line; // [*] Current line number
+ uint in_column; // [*] Current column number
+ bool in_eof; // End of file was encountered
struct json_node *next_token;
struct json_node *trivial_token;
int next_char;
+ // Formatter context
struct fastbuf *out_fb;
uint out_indent;
- uint format_options; // Public
+ uint format_options; // [*] Formatting options (a combination of JSON_FORMAT_xxx)
};
+/** Creates a new JSON context. **/
struct json_context *json_new(void);
+
+/** Deletes a JSON context, deallocating all memory associated with it. **/
void json_delete(struct json_context *js);
+
+/**
+ * Recycles a JSON context. All state is reset, allocated objects are freed.
+ * This is equivalent to mp_delete() followed by mp_new(), but it is faster
+ * and the address of the context is preserved.
+ **/
void json_reset(struct json_context *js);
+/***
+ * === JSON values
+ *
+ * Each JSON value is represented by <<struct json_node,struct json_node>>,
+ * which is either an elementary value (null, boolean, number, string),
+ * or a container (array, object) pointing to other values.
+ *
+ * A value can belong to multiple containers simultaneously, so in general,
+ * the relationships between values need not form a tree, but a directed
+ * acyclic graph.
+ *
+ * You are allowed to read contents of nodes directly, but construction
+ * and modification of nodes must be always performed using the appropriate
+ * library functions.
+ ***/
+
+/** Node types **/
enum json_node_type {
JSON_INVALID,
JSON_NULL,
JSON_STRING,
JSON_ARRAY,
JSON_OBJECT,
- // These are not real nodes, but raw tokens
+ // These are not real nodes, but raw tokens.
+ // They are not present in the tree of values, but you may see them
+ // if you call json_next_token() and friends.
JSON_BEGIN_ARRAY,
JSON_END_ARRAY,
JSON_BEGIN_OBJECT,
JSON_EOF,
};
+/** Each value is represented by a single node. **/
struct json_node {
enum json_node_type type;
- union {
+ union { // Data specific to individual value types
bool boolean;
double number;
const char *string;
- struct json_node **elements; // Growing array
- struct json_pair *pairs; // Growing array
+ struct json_node **elements; // Arrays: Growing array of values
+ struct json_pair *pairs; // Objects: Growing array of pairs
};
};
+/** Attributes of objects are stored as (key, value) pairs of this format. **/
struct json_pair {
const char *key;
struct json_node *value;
// FIXME: Hash table
};
+// Used internally
struct json_node *json_new_node(struct json_context *js, enum json_node_type type);
+/** Creates a new null value. **/
static inline struct json_node *json_new_null(struct json_context *js)
{
return json_new_node(js, JSON_NULL);
}
+/** Creates a new boolean value. **/
static inline struct json_node *json_new_bool(struct json_context *js, bool value)
{
struct json_node *n = json_new_node(js, JSON_BOOLEAN);
return n;
}
+/** Creates a new numeric value. **/
static inline struct json_node *json_new_number(struct json_context *js, double value)
{
struct json_node *n = json_new_node(js, JSON_NUMBER);
return n;
}
+/** Creates a new string value. The @value is kept only as a reference. **/
static inline struct json_node *json_new_string_ref(struct json_context *js, const char *value)
{
struct json_node *n = json_new_node(js, JSON_STRING);
return n;
}
+/** Creates a new string value, making a private copy of @value. **/
static inline struct json_node *json_new_string(struct json_context *js, const char *value)
{
return json_new_string_ref(js, mp_strdup(js->pool, value));
}
+/** Creates a new array value with no elements. **/
struct json_node *json_new_array(struct json_context *js);
+
+/** Appends a new element to the given array. **/
void json_array_append(struct json_node *array, struct json_node *elt);
+/** Creates a new object value with no attributes. **/
struct json_node *json_new_object(struct json_context *js);
-// FIXME: key must not be freed
+
+/**
+ * Adds a new (@key, @value) pair to the given object. If @key is already
+ * present, the pair is replaced. If @value is NULL, no new pair is created
+ * and a pre-existing pair is deleted.
+ *
+ * The @key is referenced by the object, you must not free it during
+ * the lifetime of the JSON context.
+ *
+ * FIXME: Add json_copy_key().
+ **/
void json_object_set(struct json_node *n, const char *key, struct json_node *value);
+
+/** Returns the value associated with @key, or NULL if no such value exists. **/
struct json_node *json_object_get(struct json_node *n, const char *key);
+/***
+ * === Parser
+ *
+ * The simplest way to parse a complete JSON file is to call json_parse(),
+ * which returns a value tree representing the contents of the file.
+ *
+ * Alternatively, you can read the input token by token: call json_set_input()
+ * and then repeat json_next_token(). Note that even in this mode, the tokens
+ * are kept in memory. (If you need to parse huge JSON files, please ask the
+ * maintainer of this library to improve memory allocation.)
+ ***/
+
+/** Parses a JSON file from the given fastbuf stream. **/
+struct json_node *json_parse(struct json_context *js, struct fastbuf *fb);
+
+/** Selects the given fastbuf stream as parser input. **/
void json_set_input(struct json_context *js, struct fastbuf *in);
-struct json_node *json_peek_token(struct json_context *js);
+
+/** Reads the next token from the input. **/
struct json_node *json_next_token(struct json_context *js);
+/** Reads the next token, but keeps it in the input. **/
+struct json_node *json_peek_token(struct json_context *js);
+
+/** Reads the next JSON value, including nested values. **/
struct json_node *json_next_value(struct json_context *js);
-struct json_node *json_parse(struct json_context *js, struct fastbuf *fb);
+/***
+ * === Writer
+ *
+ * JSON files can be produced by simply calling json_write().
+ *
+ * If you want to generate the output on the fly (for example if it is huge),
+ * call json_set_output() and then iterate json_write_value().
+ *
+ * By default, we produce a single-line compact representation,
+ * but you can choose differently by setting the appropriate
+ * `format_options` in the `json_context`.
+ ***/
+/** Writes a JSON file to the given fastbuf stream, containing the JSON value @n. **/
+void json_write(struct json_context *js, struct fastbuf *fb, struct json_node *n);
+
+/** Selects the given fastbuf stream as output. **/
void json_set_output(struct json_context *js, struct fastbuf *fb);
+
+/** Writes a single JSON value to the output stream. **/
void json_write_value(struct json_context *js, struct json_node *n);
-void json_write(struct json_context *js, struct fastbuf *fb, struct json_node *n);
+/** Formatting options. The `format_options` field in the context is a bitwise OR of these flags. **/
enum json_format_option {
- JSON_FORMAT_ESCAPE_NONASCII = 1,
- JSON_FORMAT_INDENT = 2,
+ JSON_FORMAT_ESCAPE_NONASCII = 1, // Produce pure ASCII output by escaping all Unicode characters in strings
+ JSON_FORMAT_INDENT = 2, // Produce pretty indented output
};
#endif