Simple JSON parser written in C99
- Simple, small and easy to use, integration.
- Writing in C99: simple, small, portability.
- Robust error handling, no pointer-as-object.
- Single responsibility: when work with json in C, we only need parse the json string to C value. Json stringify is easy to implement in C.
- No memory allocations, linear memory layout, cache friendly.
- Well-tested with some real-world examples.
- Visual Studio Natvis.
- String as UTF8.
- No scientific number
- Not use state machine for parsing
- Not the best, the fastest json parser
Belove code from Json_TokenTest.c:
#include "Json.h"
#include "JsonUtils.h" // for JsonPrint
int main(int argc, char* argv[])
{
signal(SIGINT, _sighandler);
printf("JSON token testing prompt\n");
printf("Type '.exit' to exit\n");
char input[1024];
int allocatorCapacity = 1024 * 1024; // 1MB temp buffer
void* allocatorBuffer = malloc(allocatorCapacity);
while (1)
{
if (setjmp(jmpenv) == 0)
{
printf("> ");
fgets(input, sizeof(input), stdin);
const char* json = strtrim_fast(input);
if (strcmp(json, ".exit") == 0)
{
break;
}
else
{
Json* value;
JsonError error = JsonParse(json, strlen(json), JsonFlags_NoStrictTopLevel, allocatorBuffer, allocatorCapacity, &value);
if (error.code != JsonError_None)
{
value = NULL;
printf("[ERROR]: %s\n", error.message);
}
else
{
JsonPrint(value, stdout); printf("\n");
}
}
}
}
free(allocatorBuffer);
return 0;
}
enum JsonType
{
JsonType_Null,
JsonType_Array,
JsonType_Object,
JsonType_Number,
JsonType_String,
JsonType_Boolean,
};
typedef enum JsonErrorCode
{
JsonError_None,
JsonError_WrongFormat,
JsonError_UnmatchToken,
JsonError_UnknownToken,
JsonError_UnexpectedToken,
JsonError_UnsupportedToken,
JsonError_OutOfMemory,
JsonError_InvalidValue,
JsonError_InternalFatal,
} JsonErrorCode;
typedef struct JsonError
{
JsonErrorCode code;
const char* message;
} JsonError;
typedef enum JsonFlags
{
JsonFlags_None = 0,
JsonFlags_SupportComment = 1 << 0,
JsonFlags_NoStrictTopLevel = 1 << 1,
} JsonFlags;
struct Json
{
JsonType type;
int length;
union
{
double number;
bool boolean;
const char* string;
Json* array;
JsonObjectMember* object;
};
};
struct JsonObjectMember
{
const char* name;
Json value;
};
static const Json JSON_NULL = { JsonType_Null , 0 };
static const Json JSON_TRUE = { JsonType_Boolean, 0, true };
static const Json JSON_FALSE = { JsonType_Boolean, 0, false };
JSON_API JsonError JsonParse(const char* jsonCode, int32_t jsonCodeLength, JsonFlags flags, void* buffer, int32_t bufferSize, Json* result);
JSON_API bool JsonEquals(const Json a, const Json b);
JSON_API bool JsonFind(const Json parent, const char* name, Json* result);
This project is a single-header-only library, user is free to choose build system. This instructions are for development only.
# Run REPL test
make test
# Run unit tests
make unit_test
# Make single header libary (when you want to make sure Json.h/JsonEx.h is the newest version)
make lib
When I first read an article about common mistake of c libraries are do much dynamic allocations and have no custom allocators. So I create this projects to learn to make good library in C.
In the first version there is a custom allocator interface. But after the long run, I found the memory of Json was throw away at one, so dynamic allocators are expensive for that. Now we just given a temporary buffer to parser, and there is a linear allocator in internal. So no dynamic allocations.
It easy to write an JsonStringify version, but the real problem in C is not that simple. You need to create Json value, create Json may need memory, so we need to care about memory allocation. That headache! Fortunately, C is static type language, so we can easily convert out data structure/object to json easily base on its types. See example below:
typedef struct Entity
{
uint32_t id;
const char* name;
} Entity;
const char* JsonifyEntity(Entity entity)
{
static char buffer[256];
snprintf(buffer, sizeof(buffer),
"{\"id\":%u,\"name\":\"%s\"}", entity.id, entity.name
);
return buffer;
}
Just rename, update, change what you not like with your code editor.
This repo use UNLICENSE but the source code you generate from make
can be your license of choice.