blob: 5077f85352e1b8bf6e414112e3053923b956a96b [file] [log] [blame]
Austin Schuhf417eaf2019-09-16 21:58:36 -07001//
2// This is an example of parsing and building strict documents into C structs.
3//
4// The general approach is that each object type has a struct type and a
5// builder function. The struct type has members which represents its
6// properties. The builder function is more intresting: It takes a tokenizer
7// state and a struct instance. The builder function then reads each field
8// name from the tokenizer and calls other builder functions (this is how this
9// parser does flow control), and eventually stores the values into the struct
10// instance.
11//
12#include <jsont.h>
13#include <stdio.h>
14#include <string.h>
15#include <stdbool.h>
16
17// A simple array type
18typedef struct my_array {
19 size_t size;
20 size_t count;
21 void** items;
22} my_array_t;
23
24// Represents a user object
25typedef struct my_user {
26 const char* id;
27 const char* name;
28} my_user_t;
29
30// Represents a response from our imaginary service
31typedef struct my_response {
32 int64_t timestamp;
33 const char* viewer_id;
34 my_array_t users;
35} my_response_t;
36
37// A helper macro for allocating a new struct instance
38#define MY_NEW(T) (T*)malloc(sizeof(T))
39
40// Some helper macros for dealing with growing arrays
41#define MY_ARRAY_ALLOC(A, _size) do {\
42 (A).items = (void*)malloc(sizeof(void*)*_size); \
43 (A).count = 0; \
44 (A).size = _size; \
45 } while(0)
46#define MY_ARRAY_RESIZE(A, _size) do {\
47 (A).items = (void*)realloc((A).items, sizeof(void*)*_size); \
48 (A).size = _size; \
49 } while(0)
50#define MY_ARRAY_APPEND(A, item) (A).items[(A).count++] = (void*)(item)
51#define MY_NEXT_EXPECT(S, TOKTYPE) do { \
52 if ((tok = jsont_next(S)) != TOKTYPE) { \
53 printf("Error: Builder expected token " #TOKTYPE " (%d)\n", __LINE__); \
54 return false; \
55 }} while (0)
56
57// Builder function for user objects
58bool my_user_build(jsont_ctx_t* S, my_user_t* obj) {
59 jsont_tok_t tok = jsont_current(S);
60 if (tok != JSONT_OBJECT_START) return false;
61
62 // for each field
63 while ((tok = jsont_next(S)) == JSONT_FIELD_NAME) {
64 const uint8_t* fieldname = 0;
65 size_t len = jsont_data_value(S, &fieldname);
66
67 if (memcmp("id", fieldname, len) == 0) {
68 MY_NEXT_EXPECT(S, JSONT_STRING);
69 obj->id = jsont_strcpy_value(S);
70
71 } else if (memcmp("name", fieldname, len) == 0) {
72 MY_NEXT_EXPECT(S, JSONT_STRING);
73 obj->name = jsont_strcpy_value(S);
74
75 } else {
76 printf("%s: Unexpected field: \"%.*s\"\n", __FUNCTION__,
77 (int)len, (const char*)fieldname);
78 return false;
79 }
80 }
81
82 return true;
83}
84
85// Builder function for response objects
86bool my_response_build(jsont_ctx_t* S, my_response_t* obj) {
87 jsont_tok_t tok = jsont_current(S);
88 if (tok != JSONT_OBJECT_START) return false;
89
90 // for each field
91 while ((tok = jsont_next(S)) == JSONT_FIELD_NAME) {
92 const uint8_t* fieldname = 0;
93 size_t len = jsont_data_value(S, &fieldname);
94
95 if (memcmp("timestamp", fieldname, len) == 0) {
96 MY_NEXT_EXPECT(S, JSONT_NUMBER_INT);
97 obj->timestamp = jsont_int_value(S);
98
99 } else if (memcmp("viewer_id", fieldname, len) == 0) {
100 MY_NEXT_EXPECT(S, JSONT_STRING);
101 obj->viewer_id = jsont_strcpy_value(S);
102
103 } else if (memcmp("users", fieldname, len) == 0) {
104 MY_NEXT_EXPECT(S, JSONT_ARRAY_START);
105 MY_ARRAY_ALLOC(obj->users, 10);
106
107 // for each user object
108 while ((tok = jsont_next(S)) == JSONT_OBJECT_START) {
109 if (obj->users.count == obj->users.size)
110 MY_ARRAY_RESIZE(obj->users, obj->users.size * 2);
111 my_user_t* user = MY_NEW(my_user_t);
112 if (!my_user_build(S, user))
113 return false;
114 MY_ARRAY_APPEND(obj->users, user);
115 }
116 } else {
117 printf("%s: Unexpected field: \"%.*s\"\n", __FUNCTION__,
118 (int)len, (const char*)fieldname);
119 return false;
120 }
121 }
122
123 return true;
124}
125
126// Our simple response parser entry point. Returns NULL on error.
127my_response_t* my_parse_response(jsont_ctx_t* S) {
128if (jsont_next(S) != JSONT_OBJECT_START) {
129 printf("Expected JSON input to start with an object.\n");
130 return 0;
131 }
132 my_response_t* rsp = MY_NEW(my_response_t);
133 if (!my_response_build(S, rsp)) {
134 free(rsp);
135 return 0;
136 }
137 return rsp;
138}
139
140int main(int argc, const char** argv) {
141 // Create a new reusable tokenizer
142 jsont_ctx_t* S = jsont_create(0);
143
144 // Sample "response" data
145 const char* inbuf = "{"
146 "\"viewer_id\": \"abc123\","
147 "\"timestamp\": 1234567890,"
148 "\"users\":["
149 "{\"name\": \"John Smith\", \"id\": \"12c39a\"},\n"
150 "{\"name\": \"John Doe\", \"id\": \"01dk2\"},\n"
151 "{\"name\": \"Kate Smith\", \"id\": \"apru1\"},\n"
152 "{\"name\": \"Rebecca Doe\",\"id\": \"aRm26\"}\n"
153 "]"
154 "}";
155
156 // Parse the sample "response" data
157 jsont_reset(S, (const uint8_t*)inbuf, strlen(inbuf));
158 my_response_t* rsp = my_parse_response(S);
159
160 // Epic success?
161 if (rsp) {
162 printf("Built response structure.\n");
163 printf("rsp->users.items[2]->name => \"%s\"\n",
164 ((my_user_t*)rsp->users.items[2])->name );
165
166 } else {
167 printf("Failed to build response structure.\n");
168 if (jsont_error_info(S) != 0) {
169 fprintf(stderr, "Error: %s ('%c' at offset %lu)\n",
170 jsont_error_info(S),
171 (char)jsont_current_byte(S),
172 (unsigned long)jsont_current_offset(S));
173 }
174 // Exit with error. Note: In a real application, you should call
175 // `jsont_destroy` on the reusable tokenizer when done with it. Here we
176 // just exit the program.
177 return 1;
178 }
179
180 // Destroy our reusable tokenizer and exit
181 jsont_destroy(S);
182 return 0;
183}