blob: 5077f85352e1b8bf6e414112e3053923b956a96b [file] [log] [blame]
//
// This is an example of parsing and building strict documents into C structs.
//
// The general approach is that each object type has a struct type and a
// builder function. The struct type has members which represents its
// properties. The builder function is more intresting: It takes a tokenizer
// state and a struct instance. The builder function then reads each field
// name from the tokenizer and calls other builder functions (this is how this
// parser does flow control), and eventually stores the values into the struct
// instance.
//
#include <jsont.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
// A simple array type
typedef struct my_array {
size_t size;
size_t count;
void** items;
} my_array_t;
// Represents a user object
typedef struct my_user {
const char* id;
const char* name;
} my_user_t;
// Represents a response from our imaginary service
typedef struct my_response {
int64_t timestamp;
const char* viewer_id;
my_array_t users;
} my_response_t;
// A helper macro for allocating a new struct instance
#define MY_NEW(T) (T*)malloc(sizeof(T))
// Some helper macros for dealing with growing arrays
#define MY_ARRAY_ALLOC(A, _size) do {\
(A).items = (void*)malloc(sizeof(void*)*_size); \
(A).count = 0; \
(A).size = _size; \
} while(0)
#define MY_ARRAY_RESIZE(A, _size) do {\
(A).items = (void*)realloc((A).items, sizeof(void*)*_size); \
(A).size = _size; \
} while(0)
#define MY_ARRAY_APPEND(A, item) (A).items[(A).count++] = (void*)(item)
#define MY_NEXT_EXPECT(S, TOKTYPE) do { \
if ((tok = jsont_next(S)) != TOKTYPE) { \
printf("Error: Builder expected token " #TOKTYPE " (%d)\n", __LINE__); \
return false; \
}} while (0)
// Builder function for user objects
bool my_user_build(jsont_ctx_t* S, my_user_t* obj) {
jsont_tok_t tok = jsont_current(S);
if (tok != JSONT_OBJECT_START) return false;
// for each field
while ((tok = jsont_next(S)) == JSONT_FIELD_NAME) {
const uint8_t* fieldname = 0;
size_t len = jsont_data_value(S, &fieldname);
if (memcmp("id", fieldname, len) == 0) {
MY_NEXT_EXPECT(S, JSONT_STRING);
obj->id = jsont_strcpy_value(S);
} else if (memcmp("name", fieldname, len) == 0) {
MY_NEXT_EXPECT(S, JSONT_STRING);
obj->name = jsont_strcpy_value(S);
} else {
printf("%s: Unexpected field: \"%.*s\"\n", __FUNCTION__,
(int)len, (const char*)fieldname);
return false;
}
}
return true;
}
// Builder function for response objects
bool my_response_build(jsont_ctx_t* S, my_response_t* obj) {
jsont_tok_t tok = jsont_current(S);
if (tok != JSONT_OBJECT_START) return false;
// for each field
while ((tok = jsont_next(S)) == JSONT_FIELD_NAME) {
const uint8_t* fieldname = 0;
size_t len = jsont_data_value(S, &fieldname);
if (memcmp("timestamp", fieldname, len) == 0) {
MY_NEXT_EXPECT(S, JSONT_NUMBER_INT);
obj->timestamp = jsont_int_value(S);
} else if (memcmp("viewer_id", fieldname, len) == 0) {
MY_NEXT_EXPECT(S, JSONT_STRING);
obj->viewer_id = jsont_strcpy_value(S);
} else if (memcmp("users", fieldname, len) == 0) {
MY_NEXT_EXPECT(S, JSONT_ARRAY_START);
MY_ARRAY_ALLOC(obj->users, 10);
// for each user object
while ((tok = jsont_next(S)) == JSONT_OBJECT_START) {
if (obj->users.count == obj->users.size)
MY_ARRAY_RESIZE(obj->users, obj->users.size * 2);
my_user_t* user = MY_NEW(my_user_t);
if (!my_user_build(S, user))
return false;
MY_ARRAY_APPEND(obj->users, user);
}
} else {
printf("%s: Unexpected field: \"%.*s\"\n", __FUNCTION__,
(int)len, (const char*)fieldname);
return false;
}
}
return true;
}
// Our simple response parser entry point. Returns NULL on error.
my_response_t* my_parse_response(jsont_ctx_t* S) {
if (jsont_next(S) != JSONT_OBJECT_START) {
printf("Expected JSON input to start with an object.\n");
return 0;
}
my_response_t* rsp = MY_NEW(my_response_t);
if (!my_response_build(S, rsp)) {
free(rsp);
return 0;
}
return rsp;
}
int main(int argc, const char** argv) {
// Create a new reusable tokenizer
jsont_ctx_t* S = jsont_create(0);
// Sample "response" data
const char* inbuf = "{"
"\"viewer_id\": \"abc123\","
"\"timestamp\": 1234567890,"
"\"users\":["
"{\"name\": \"John Smith\", \"id\": \"12c39a\"},\n"
"{\"name\": \"John Doe\", \"id\": \"01dk2\"},\n"
"{\"name\": \"Kate Smith\", \"id\": \"apru1\"},\n"
"{\"name\": \"Rebecca Doe\",\"id\": \"aRm26\"}\n"
"]"
"}";
// Parse the sample "response" data
jsont_reset(S, (const uint8_t*)inbuf, strlen(inbuf));
my_response_t* rsp = my_parse_response(S);
// Epic success?
if (rsp) {
printf("Built response structure.\n");
printf("rsp->users.items[2]->name => \"%s\"\n",
((my_user_t*)rsp->users.items[2])->name );
} else {
printf("Failed to build response structure.\n");
if (jsont_error_info(S) != 0) {
fprintf(stderr, "Error: %s ('%c' at offset %lu)\n",
jsont_error_info(S),
(char)jsont_current_byte(S),
(unsigned long)jsont_current_offset(S));
}
// Exit with error. Note: In a real application, you should call
// `jsont_destroy` on the reusable tokenizer when done with it. Here we
// just exit the program.
return 1;
}
// Destroy our reusable tokenizer and exit
jsont_destroy(S);
return 0;
}