commit 156d4fea7a379ad72bcc4ad84faba14c70c21355 Author: Simon Riggs Date: Tue Jan 19 20:25:55 2021 +0000 Fix and improve context reporting for JSON parsing errors. Use the original input line number to report JSON parsing errors and reduce the amount of extraneous context for mult-line issues. Add tests for invalid multi-line JSON to illustrate problem. These changes mean a JSON error such as this { "one": 1, "two":,"two", <-- extra comma "three": true } which was previously reported as CONTEXT: JSON data, line 1: { "one": 1, "two":,... but is now reported as CONTEXT: JSON data, line 3: "two":,... Backpatch to all supported versions, with suitable modifications. diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index a794a7df84..2ff0159ad3 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -638,17 +638,15 @@ report_json_context(JsonLexContext *lex) const char *context_start; const char *context_end; const char *line_start; - int line_number; char *ctxt; int ctxtlen; const char *prefix; const char *suffix; /* Choose boundaries for the part of the input we will display */ - context_start = lex->input; + context_start = lex->input + lex->pos_last_linefeed; context_end = lex->token_terminator; line_start = context_start; - line_number = 1; for (;;) { /* Always advance over newlines */ @@ -656,7 +654,6 @@ report_json_context(JsonLexContext *lex) { context_start++; line_start = context_start; - line_number++; continue; } /* Otherwise, done as soon as we are close enough to context_end */ @@ -691,7 +688,7 @@ report_json_context(JsonLexContext *lex) suffix = (lex->token_type != JSON_TOKEN_END && context_end - lex->input < lex->input_length && *context_end != '\n' && *context_end != '\r') ? "..." : ""; return errcontext("JSON data, line %d: %s%s%s", - line_number, prefix, ctxt, suffix); + lex->line_number, prefix, ctxt, suffix); } diff --git a/src/common/jsonapi.c b/src/common/jsonapi.c index 831a44a2da..648e85cbe2 100644 --- a/src/common/jsonapi.c +++ b/src/common/jsonapi.c @@ -158,6 +158,7 @@ makeJsonLexContextCstringLen(char *json, int len, int encoding, bool need_escape lex->input = lex->token_terminator = lex->line_start = json; lex->line_number = 1; + lex->pos_last_linefeed = 0; lex->input_length = len; lex->input_encoding = encoding; if (need_escapes) @@ -536,7 +537,10 @@ json_lex(JsonLexContext *lex) (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r')) { if (*s == '\n') + { ++lex->line_number; + lex->pos_last_linefeed = len; + } ++s; ++len; } diff --git a/src/include/common/jsonapi.h b/src/include/common/jsonapi.h index 03331f6d13..3baef0411c 100644 --- a/src/include/common/jsonapi.h +++ b/src/include/common/jsonapi.h @@ -79,6 +79,7 @@ typedef struct JsonLexContext char *prev_token_terminator; JsonTokenType token_type; int lex_level; + int pos_last_linefeed; int line_number; char *line_start; StringInfo strval; diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index c4156cf2a6..ff15acf3c0 100644 --- a/src/test/regress/expected/json.out +++ b/src/test/regress/expected/json.out @@ -272,6 +272,42 @@ LINE 1: SELECT ' '::json; ^ DETAIL: The input string ended unexpectedly. CONTEXT: JSON data, line 1: +-- Multi-line JSON input to check ERROR reporting +SELECT jsonb_pretty('{ + "one": 1, + "two":"two", + "three": + true}'::jsonb); -- OK + jsonb_pretty +------------------- + { + + "one": 1, + + "two": "two",+ + "three": true+ + } +(1 row) + +SELECT jsonb_pretty('{ + "one": 1, + "two":,"two", -- ERROR extraneous comma before field "two" value, line 3 + "three": + true}'::jsonb); +ERROR: invalid input syntax for type json +LINE 1: SELECT jsonb_pretty('{ + ^ +DETAIL: Expected JSON value, but found ",". +CONTEXT: JSON data, line 3: "two":,... +SELECT jsonb_pretty('{ + "one": 1, + "two":"two", + "three": + }'::jsonb); -- ERROR missing value for field "three", line 5 +ERROR: invalid input syntax for type json +LINE 1: SELECT jsonb_pretty('{ + ^ +DETAIL: Expected JSON value, but found "}". +CONTEXT: JSON data, line 5: } +-- ERROR is on line 5 because a LF \n is legal whitespace at end of line 4, so the missing syntax is on line 5 --constructors -- array_to_json SELECT array_to_json(array(select 1 as a)); diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql index 20354f04e3..e15697c95a 100644 --- a/src/test/regress/sql/json.sql +++ b/src/test/regress/sql/json.sql @@ -59,6 +59,24 @@ SELECT 'trues'::json; -- ERROR, not a keyword SELECT ''::json; -- ERROR, no value SELECT ' '::json; -- ERROR, no value +-- Multi-line JSON input to check ERROR reporting +SELECT jsonb_pretty('{ + "one": 1, + "two":"two", + "three": + true}'::jsonb); -- OK +SELECT jsonb_pretty('{ + "one": 1, + "two":,"two", -- ERROR extraneous comma before field "two" value, line 3 + "three": + true}'::jsonb); +SELECT jsonb_pretty('{ + "one": 1, + "two":"two", + "three": + }'::jsonb); -- ERROR missing value for field "three", line 5 +-- ERROR is on line 5 because a LF \n is legal whitespace at end of line 4, so the missing syntax is on line 5 + --constructors -- array_to_json