/* * Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net> * * 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 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. */ /* * Json.h: A fully-featured JSON API for C using Arrays and HashMaps. * This API builds on the foundations of Arrays and HashMaps, because * that's all a JSON object really is. It provides a JsonValue, which * is used to encapsulate arbitrary values while being able to identify * them in the future, so that JSON can be effectively handled. * * This implementation is just to get the job done in parsing and * generating JSON. It is extremely strict; it will fail on syntax * errors. This is fine for Matrix, because we can just return * M_BAD_JSON anything in here fails. * * One thing to note about this implementation is that it focuses * primarily on serialization and deserialization to and from streams. * What this means is that it does not provide facilities for handling * JSON strings; it only writes JSON to output streams, and reading * them from input streams. Of course, you could use the POSIX * fmemopen() and open_memstream() functions if you really want to deal * with JSON strings, but JSON is intended to be an exchange format. * Data should be converted to JSON when it is leaving, and converted * from JSON when it is coming in. Ideally, most of the program would * have no idea what JSON actually is. */ #ifndef TELODENDRIA_JSON_H #define TELODENDRIA_JSON_H #include #include #include #include /* * All the possible JSON types. This enumeration is used to identify * the type of the value stored in a JsonValue. */ typedef enum JsonType { JSON_NULL, /* Maps to nothing. */ JSON_OBJECT, /* Maps to a HashMap of JsonValues */ JSON_ARRAY, /* Maps to an Array of JsonValues */ JSON_STRING, /* Maps to a C string */ JSON_INTEGER, /* Maps to a C long */ JSON_FLOAT, /* Maps to a C double */ JSON_BOOLEAN /* Maps to a C 1 or 0 */ } JsonType; /* * A JsonValue encapsulates all the possible values that can be stored * in a JSON object as a single type, so as to provide a consistent * API for accessing and setting them. It is an opaque structure that * can be managed entirely by the functions defined in this API. * * Note that in the case of objects, arrays, and strings, this structure * only stores pointers to allocated data, it doesn't store the data * itself. JsonValues only store integers, floats, booleans, and NULL * in their memory. Anything else must be freed separately. */ typedef struct JsonValue JsonValue; /* * Get the type of a JsonValue. * * Params: * * (JsonValue *) The value to get the type of. * * Return: A JsonType that tells what the provided value is, or * JSON_NULL if the passed value is NULL. Note that even a fully * valid JsonValue may still be of type JSON_NULL, so this function * should not be used to check whether or not the JSON value is valid. */ extern JsonType JsonValueType(JsonValue *); /* * Wrap a HashMap into a JsonValue that represents a JSON object. Note * that the HashMap should contain only JsonValues. Any other contents * are not supported and will lead to undefined behavior. * * Params: * * (HashMap *) The hash map of JsonValues to wrap in a JsonValue. * * Return: A JsonValue that holds a pointer to the given object, or * NULL if there was an error allocating memory. */ extern JsonValue * JsonValueObject(HashMap *); /* * Get a HashMap from a JsonValue that represents a JSON object. * * Params: * * (JsonValue *) The value to extract the object from. * * Return: A HashMap of JsonValues, or NULL if no value was provided, * or the value is not of type JSON_OBJECT. */ extern HashMap * JsonValueAsObject(JsonValue *); /* * The following methods very closely resemble the ones above, and * behave pretty much the exact same. To save on time and effort, * I'm choosing not to explicitly document all of these. If something * is unclear about how these functions work, consult the source code, * and then feel free to write the documentation yourself. * * Otherwise, reach out to the official Matrix room, and someone will * be able to help you. */ extern JsonValue * JsonValueArray(Array * array); extern Array * JsonValueAsArray(JsonValue * value); extern JsonValue * JsonValueString(char *string); extern JsonValue * JsonValueInteger(long integer); extern JsonValue * JsonValueFloat(double floating); extern JsonValue * JsonValueBoolean(int boolean); /* * Create a JsonValue that represents a JSON null. Because Arrays and * HashMaps should not contain NULL values, I thought it appropriate * to provide support for JSON nulls. Yes, a small amount of memory is * allocated just to point to a NULL, but this keeps all the APIs * clean. * * Return: A JsonValue that represents a JSON null, or NULL if memory * could not be allocated. */ extern JsonValue * JsonValueNull(void); /* * Free the memory being used by a JSON value. Note that this will * recursively free all Arrays, HashMaps, and other JsonValues that * are reachable from this one. It will invoke free() on strings as * well, so make sure passed string pointers point to strings on the * heap, not the stack. This will be the case for all strings returned * by JsonDecode(), which is why this assumption is made. However, if * you are manually creating JsonObjects and stitching them together, * you'll have to manually free them as well. Calling this on a * JsonValue that contains a pointer to a stack string is undefined. * * Params: * * (JsonValue *) The JsonValue to recursively free. */ extern void JsonValueFree(JsonValue *); /* * Recursively free a HashMap of JsonValues. This iterates over all * the JsonValues in a HashMap and frees them using JsonValueFree(), * which will in turn call JsonFree() on values of type JSON_OBJECT. * * Params: * * (HashMap *) The hash map of JsonValues to recursively free. */ extern void JsonFree(HashMap *); /* * Encode the given string in such a way that it can be embedded in a * JSON stream. This entails: * * - Escaping quotes, backslashes, and other special characters using * their backslash escape * - Encoding bytes that are not UTF-8 using \u escapes. * - Wrapping the entire string in double quotes. * * This function is provided via the public API so it is accessible to * custom JSON encoders, such as the CanonicalJson API. This will * typically be used for encoding JSON keys; for values, just use * JsonEncodeValue(). * * Params: * * (const char *) The C string to serialize as a JSON string. * (FILE *) The output stream to write the encoded string to. */ extern void JsonEncodeString(const char *, FILE *); /* * Serialize a JsonValue as it would appear in JSON output. This is * a recursive function that will also encode all child values * reachable from the given JsonValue. * * This is exposed via the public API so that custom JSON encoders * such as CanonicalJson can take advantage of it. Normal users that * are writing custom encoders should just use JsonEncode() to encode * an entire object. * * Params: * * (JsonValue *) The value to encode. * (FILE *) The output stream to write the given value to. */ extern void JsonEncodeValue(JsonValue * value, FILE * out); /* * Encode a HashMap of JsonValues into a fully-valid, minimized JSON * object. This function is recursive; it will serialize everything * accessible from the passed object into JSON. * * Params: * * (HashMap *) The HashMap of JsonValues to encode and write to the * output stream. * (FILE *) The output stream to write the given HashMap to. * * Return: Whether or not the operation was successful. This function * will fail if either the passed HashMap or file stream are NULL. In * all other cases, this function succeeds. */ extern int JsonEncode(HashMap *, FILE *); /* * Decode the given input stream into a HashMap of JsonValues. * * Params: * * (FILE *) The input stream to parse JSON from. * * Return: A HashMap of JsonValues, or NULL if there was an error * parsing the JSON. */ extern HashMap * JsonDecode(FILE *); #endif