Skip to content

Commit 60fa2a7

Browse files
committed
Add location information to decoded JSON objects
Aid users in printing semantic errors in JSON input by providing them with location data for each parsed JSON element. An example usage of such data is demonstrated by nftables' error messages: | # nft add chain mytable mychain | Error: No such file or directory | add chain mytable mychain | ^^^^^^^ To reduce overhead for library users not interested in such data, store it only if `JSON_STORE_LOCATION' flag was passed to json_load*() functions. It may then be retrieved using the new API function json_get_location(). Signed-off-by: Phil Sutter <psutter@redhat.com>
1 parent 9535972 commit 60fa2a7

File tree

10 files changed

+325
-0
lines changed

10 files changed

+325
-0
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ if (NOT JANSSON_WITHOUT_TESTS)
490490
test_load
491491
test_load_callback
492492
test_loadb
493+
test_location
493494
test_number
494495
test_object
495496
test_pack

doc/apiref.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2068,3 +2068,36 @@ And usage::
20682068

20692069
json_decref(obj);
20702070
}
2071+
2072+
.. _apiref-location-information:
2073+
2074+
Location Information
2075+
====================
2076+
2077+
Jansson supports storing decoded objects' locations in input for better
2078+
reporting of semantic errors in applications. Since this comes with a certain
2079+
overhead, location information is stored only if ``JSON_STORE_LOCATION`` flag
2080+
was specified during decoding.
2081+
2082+
.. function:: int json_get_location(json_t *json, int *line, int *column, int *position, int *length);
2083+
2084+
Retrieve location of *json* writing it to memory locations pointed to by
2085+
*line*, *column*, *position* and *length* if not *NULL*. Returns 0 on success
2086+
or -1 if no location information is available for *json*.
2087+
2088+
``line``
2089+
The line number on which the object occurred.
2090+
2091+
``column``
2092+
The column on which the object occurred. Note that this is the *character
2093+
column*, not the byte column, i.e. a multibyte UTF-8 character counts as one
2094+
column.
2095+
2096+
``position``
2097+
The position in bytes from the start of the input. This is useful for
2098+
debugging Unicode encoding problems.
2099+
2100+
``length``
2101+
The length of the object in bytes. For arrays and objects, length is always
2102+
1. For all other types, the value resembles the actual length as it appears
2103+
in input. Note that for strings, this includes the quotes.

src/jansson.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,5 @@ EXPORTS
8080
json_get_alloc_funcs
8181
jansson_version_str
8282
jansson_version_cmp
83+
json_get_location
8384

src/jansson.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,10 @@ void json_get_alloc_funcs(json_malloc_t *malloc_fn, json_free_t *free_fn);
413413
const char *jansson_version_str(void);
414414
int jansson_version_cmp(int major, int minor, int micro);
415415

416+
/* location information */
417+
418+
int json_get_location(json_t *json, int *line, int *column, int *position, int *length);
419+
416420
#ifdef __cplusplus
417421
}
418422
#endif

src/jansson_private.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ int jsonp_loop_check(hashtable_t *parents, const json_t *json, char *key, size_t
101101

102102
/* Helpers for location information */
103103
json_t *jsonp_simple(json_t *json, size_t flags);
104+
void jsonp_store_location(json_t *json, int line, int column,
105+
int position, int length);
104106

105107
/* Windows compatibility */
106108
#if defined(_WIN32) || defined(WIN32)

src/load.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,22 @@ static void lex_close(lex_t *lex) {
655655
strbuffer_close(&lex->saved_text);
656656
}
657657

658+
static void store_location_from_lex(json_t *json, size_t flags, const lex_t *lex)
659+
{
660+
int tlen = lex->saved_text.length;
661+
662+
if (!(flags & JSON_STORE_LOCATION))
663+
return;
664+
665+
if (tlen)
666+
tlen--;
667+
668+
jsonp_store_location(json, lex->stream.line,
669+
lex->stream.column - tlen,
670+
lex->stream.position - tlen,
671+
tlen + 1);
672+
}
673+
658674
/*** parser ***/
659675

660676
static json_t *parse_value(lex_t *lex, size_t flags, json_error_t *error);
@@ -664,6 +680,7 @@ static json_t *parse_object(lex_t *lex, size_t flags, json_error_t *error) {
664680
if (!object)
665681
return NULL;
666682

683+
store_location_from_lex(object, flags, lex);
667684
lex_scan(lex, error);
668685
if (lex->token == '}')
669686
return object;
@@ -741,6 +758,7 @@ static json_t *parse_array(lex_t *lex, size_t flags, json_error_t *error) {
741758
if (!array)
742759
return NULL;
743760

761+
store_location_from_lex(array, flags, lex);
744762
lex_scan(lex, error);
745763
if (lex->token == ']')
746764
return array;
@@ -796,31 +814,37 @@ static json_t *parse_value(lex_t *lex, size_t flags, json_error_t *error) {
796814
}
797815

798816
json = jsonp_stringn_nocheck_own(value, len);
817+
store_location_from_lex(json, flags, lex);
799818
lex->value.string.val = NULL;
800819
lex->value.string.len = 0;
801820
break;
802821
}
803822

804823
case TOKEN_INTEGER: {
805824
json = json_integer(lex->value.integer);
825+
store_location_from_lex(json, flags, lex);
806826
break;
807827
}
808828

809829
case TOKEN_REAL: {
810830
json = json_real(lex->value.real);
831+
store_location_from_lex(json, flags, lex);
811832
break;
812833
}
813834

814835
case TOKEN_TRUE:
815836
json = jsonp_simple(json_true(), flags);
837+
store_location_from_lex(json, flags, lex);
816838
break;
817839

818840
case TOKEN_FALSE:
819841
json = jsonp_simple(json_false(), flags);
842+
store_location_from_lex(json, flags, lex);
820843
break;
821844

822845
case TOKEN_NULL:
823846
json = jsonp_simple(json_null(), flags);
847+
store_location_from_lex(json, flags, lex);
824848
break;
825849

826850
case '{':

src/value.c

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,10 +1026,13 @@ static void json_delete_simple(json_simple_t *simple)
10261026

10271027
/*** deletion ***/
10281028

1029+
static void delete_location(json_t *json);
1030+
10291031
void json_delete(json_t *json) {
10301032
if (!json)
10311033
return;
10321034

1035+
delete_location(json);
10331036
switch (json_typeof(json)) {
10341037
case JSON_OBJECT:
10351038
json_delete_object(json_to_object(json));
@@ -1148,3 +1151,99 @@ json_t *do_deep_copy(const json_t *json, hashtable_t *parents) {
11481151
return NULL;
11491152
}
11501153
}
1154+
1155+
/*** location information ***/
1156+
1157+
typedef struct {
1158+
json_t json; /* just to integrate with hashtable */
1159+
int line;
1160+
int column;
1161+
int position;
1162+
int length;
1163+
} json_location_t;
1164+
1165+
static hashtable_t location_hash;
1166+
static int location_hash_initialized;
1167+
1168+
static void location_atexit(void)
1169+
{
1170+
if (location_hash_initialized)
1171+
hashtable_close(&location_hash);
1172+
}
1173+
1174+
void jsonp_store_location(json_t *json, int line, int column,
1175+
int position, int length)
1176+
{
1177+
json_location_t *loc = NULL;
1178+
1179+
/* not possible to store location for the singleton primitives
1180+
* as one can't distinguish them by their memory location */
1181+
if (json->refcount == (size_t)-1)
1182+
return;
1183+
1184+
if (!location_hash_initialized) {
1185+
if (!hashtable_seed) {
1186+
/* Autoseed */
1187+
json_object_seed(0);
1188+
}
1189+
if (hashtable_init(&location_hash))
1190+
return;
1191+
1192+
atexit(location_atexit);
1193+
location_hash_initialized = 1;
1194+
} else {
1195+
loc = hashtable_get(&location_hash, (void *)&json, sizeof(json));
1196+
}
1197+
if (!loc) {
1198+
loc = jsonp_malloc(sizeof(*loc));
1199+
if (!loc)
1200+
return;
1201+
1202+
loc->json.refcount = (size_t)-1;
1203+
1204+
if (hashtable_set(&location_hash,
1205+
(void *)&json, sizeof(json), (void *)loc))
1206+
return;
1207+
}
1208+
1209+
loc->line = line;
1210+
loc->column = column;
1211+
loc->position = position;
1212+
loc->length = length;
1213+
}
1214+
1215+
int json_get_location(json_t *json, int *line, int *column,
1216+
int *position, int *length)
1217+
{
1218+
json_location_t *loc = NULL;
1219+
1220+
if (location_hash_initialized)
1221+
loc = hashtable_get(&location_hash, (void *)&json, sizeof(json));
1222+
1223+
if (!loc)
1224+
return -1;
1225+
1226+
if (line)
1227+
*line = loc->line;
1228+
if (column)
1229+
*column = loc->column;
1230+
if (position)
1231+
*position = loc->position;
1232+
if (length)
1233+
*length = loc->length;
1234+
1235+
return 0;
1236+
}
1237+
1238+
static void delete_location(json_t *json)
1239+
{
1240+
struct json_location_t *loc;
1241+
1242+
if (!location_hash_initialized)
1243+
return;
1244+
1245+
loc = hashtable_get(&location_hash, (void *)&json, sizeof(json));
1246+
hashtable_del(&location_hash, (void *)&json, sizeof(json));
1247+
if (loc)
1248+
jsonp_free(loc);
1249+
}

test/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ suites/api/test_fixed_size
1111
suites/api/test_load
1212
suites/api/test_load_callback
1313
suites/api/test_loadb
14+
suites/api/test_location
1415
suites/api/test_memory_funcs
1516
suites/api/test_number
1617
suites/api/test_object

test/suites/api/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ check_PROGRAMS = \
1111
test_load \
1212
test_load_callback \
1313
test_loadb \
14+
test_location \
1415
test_memory_funcs \
1516
test_number \
1617
test_object \
@@ -28,6 +29,7 @@ test_dump_callback_SOURCES = test_dump_callback.c util.h
2829
test_fixed_size_SOURCES = test_fixed_size.c util.h
2930
test_load_SOURCES = test_load.c util.h
3031
test_loadb_SOURCES = test_loadb.c util.h
32+
test_location_SOURCES = test_location.c util.h
3133
test_memory_funcs_SOURCES = test_memory_funcs.c util.h
3234
test_number_SOURCES = test_number.c util.h
3335
test_object_SOURCES = test_object.c util.h

0 commit comments

Comments
 (0)