diff --git a/build/error.yaml b/build/error.yaml
index 25cfc1f3c..4ef84bf65 100644
--- a/build/error.yaml
+++ b/build/error.yaml
@@ -86,6 +86,9 @@ path-close: 97
# Unable to get info for a file
file-info: 98
+# Invalid JSON format. Eventually this should be a child of format error and share the same code
+json-format: 99
+
# This error should not be thrown directly -- it serves as a parent for the C errors
runtime: 122
diff --git a/doc/xml/release.xml b/doc/xml/release.xml
index 0c0fee0c5..0096f8ea6 100644
--- a/doc/xml/release.xml
+++ b/doc/xml/release.xml
@@ -53,6 +53,10 @@
Add uint64
variant type and supporting conversion functions.
+
+ Add basic C JSON parser.
+
+
Allow Buffer
object used size
to be different than allocated size
. Add functions to manage used size and remaining size and update automatically when possible.
diff --git a/lib/pgBackRest/Common/ExceptionAuto.pm b/lib/pgBackRest/Common/ExceptionAuto.pm
index 2fbf0b707..45ab39eca 100644
--- a/lib/pgBackRest/Common/ExceptionAuto.pm
+++ b/lib/pgBackRest/Common/ExceptionAuto.pm
@@ -165,6 +165,8 @@ use constant ERROR_PATH_CLOSE => 97;
push @EXPORT, qw(ERROR_PATH_CLOSE);
use constant ERROR_FILE_INFO => 98;
push @EXPORT, qw(ERROR_FILE_INFO);
+use constant ERROR_JSON_FORMAT => 99;
+push @EXPORT, qw(ERROR_JSON_FORMAT);
use constant ERROR_RUNTIME => 122;
push @EXPORT, qw(ERROR_RUNTIME);
use constant ERROR_INVALID => 123;
diff --git a/src/Makefile b/src/Makefile
index 1d02e2143..8a96fe563 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -214,6 +214,9 @@ common/type/buffer.o: common/type/buffer.c common/assert.h common/debug.h common
common/type/convert.o: common/type/convert.c common/assert.h common/debug.h common/error.auto.h common/error.h common/logLevel.h common/stackTrace.h common/type/convert.h
$(CC) $(CFLAGS) -c common/type/convert.c -o common/type/convert.o
+common/type/json.o: common/type/json.c common/debug.h common/log.h common/logLevel.h common/stackTrace.h common/type/convert.h common/type/json.h common/type/keyValue.h common/type/variantList.h
+ $(CC) $(CFLAGS) -c common/type/json.c -o common/type/json.o
+
common/type/keyValue.o: common/type/keyValue.c common/debug.h common/error.auto.h common/error.h common/logLevel.h common/memContext.h common/stackTrace.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/list.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h
$(CC) $(CFLAGS) -c common/type/keyValue.c -o common/type/keyValue.o
diff --git a/src/common/error.auto.c b/src/common/error.auto.c
index 4ec079df6..eeb97031d 100644
--- a/src/common/error.auto.c
+++ b/src/common/error.auto.c
@@ -80,6 +80,7 @@ ERROR_DEFINE( 95, CipherError, RuntimeError);
ERROR_DEFINE( 96, ParamInvalidError, RuntimeError);
ERROR_DEFINE( 97, PathCloseError, RuntimeError);
ERROR_DEFINE( 98, FileInfoError, RuntimeError);
+ERROR_DEFINE( 99, JsonFormatError, RuntimeError);
ERROR_DEFINE(122, RuntimeError, RuntimeError);
ERROR_DEFINE(123, InvalidError, RuntimeError);
ERROR_DEFINE(124, UnhandledError, RuntimeError);
@@ -163,6 +164,7 @@ static const ErrorType *errorTypeList[] =
&ParamInvalidError,
&PathCloseError,
&FileInfoError,
+ &JsonFormatError,
&RuntimeError,
&InvalidError,
&UnhandledError,
diff --git a/src/common/error.auto.h b/src/common/error.auto.h
index 697ea8b4d..1785e9362 100644
--- a/src/common/error.auto.h
+++ b/src/common/error.auto.h
@@ -82,6 +82,7 @@ ERROR_DECLARE(CipherError);
ERROR_DECLARE(ParamInvalidError);
ERROR_DECLARE(PathCloseError);
ERROR_DECLARE(FileInfoError);
+ERROR_DECLARE(JsonFormatError);
ERROR_DECLARE(RuntimeError);
ERROR_DECLARE(InvalidError);
ERROR_DECLARE(UnhandledError);
diff --git a/src/common/type/json.c b/src/common/type/json.c
new file mode 100644
index 000000000..b6900d1ad
--- /dev/null
+++ b/src/common/type/json.c
@@ -0,0 +1,111 @@
+/***********************************************************************************************************************************
+Convert JSON to/from KeyValue
+***********************************************************************************************************************************/
+#include
+
+#include "common/debug.h"
+#include "common/log.h"
+#include "common/type/json.h"
+
+/***********************************************************************************************************************************
+Convert JSON to KeyValue object
+
+Currently this function is only intended to convert the limited types that are included in info files. More types will be added as
+needed. Since this function is only intended to read internally-generated JSON it is assumed to be well-formed with no extraneous
+whitespace.
+***********************************************************************************************************************************/
+KeyValue *jsonToKv(const String *json)
+{
+ FUNCTION_DEBUG_BEGIN(logLevelTrace);
+ FUNCTION_DEBUG_PARAM(STRING, json);
+
+ FUNCTION_TEST_ASSERT(json != NULL);
+ FUNCTION_DEBUG_END();
+
+ KeyValue *result = kvNew();
+
+ MEM_CONTEXT_TEMP_BEGIN()
+ {
+ // We'll examine the string byte by byte
+ const char *jsonC = strPtr(json);
+ unsigned int jsonPos = 0;
+
+ // Consume the initial delimiter
+ if (jsonC[jsonPos] == '[')
+ THROW(JsonFormatError, "arrays not supported");
+ else if (jsonC[jsonPos] != '{')
+ THROW_FMT(JsonFormatError, "expected '{' but found '%c'", jsonC[jsonPos]);
+
+ // Start parsing key/value pairs
+ do
+ {
+ Variant *value = NULL;
+
+ jsonPos++;
+
+ // Parse the key which should always be quoted
+ if (jsonC[jsonPos] != '"')
+ THROW_FMT(JsonFormatError, "expected '\"' but found '%c'", jsonC[jsonPos]);
+
+ unsigned int keyBeginPos = ++jsonPos;
+
+ while (jsonC[jsonPos] != '"' && jsonPos < strSize(json) - 1)
+ jsonPos++;
+
+ if (jsonC[jsonPos] != '"')
+ THROW_FMT(JsonFormatError, "expected '\"' but found '%c'", jsonC[jsonPos]);
+
+ String *key = strNewN(jsonC + keyBeginPos, jsonPos - keyBeginPos);
+
+ if (strSize(key) == 0)
+ THROW(JsonFormatError, "zero-length key not allowed");
+
+ if (jsonC[++jsonPos] != ':')
+ THROW_FMT(JsonFormatError, "expected ':' but found '%c'", jsonC[jsonPos]);
+
+ unsigned int valueBeginPos = ++jsonPos;
+
+ // The value appears to be a string
+ if (jsonC[jsonPos] == '"')
+ {
+ valueBeginPos++;
+ jsonPos++;
+
+ while (jsonC[jsonPos] != '"' && jsonPos < strSize(json) - 1)
+ jsonPos++;
+
+ if (jsonC[jsonPos] != '"')
+ THROW_FMT(JsonFormatError, "expected '\"' but found '%c'", jsonC[jsonPos]);
+
+ value = varNewStr(strNewN(jsonC + valueBeginPos, jsonPos - valueBeginPos));
+
+ jsonPos++;
+ }
+
+ // The value appears to be a number
+ else if (isdigit(jsonC[jsonPos]))
+ {
+ while (isdigit(jsonC[jsonPos]) && jsonPos < strSize(json) - 1)
+ jsonPos++;
+
+ String *valueStr = strNewN(jsonC + valueBeginPos, jsonPos - valueBeginPos);
+
+ value = varNewUInt64(cvtZToUInt64(strPtr(valueStr)));
+ }
+
+ // Else not sure what it is. Currently booleans and nulls will error.
+ else
+ THROW(JsonFormatError, "unknown value type");
+
+ kvPut(result, varNewStr(key), value);
+ }
+ while (jsonC[jsonPos] == ',');
+
+ // Look for end delimiter
+ if (jsonC[jsonPos] != '}')
+ THROW_FMT(JsonFormatError, "expected '}' but found '%c'", jsonC[jsonPos]);
+ }
+ MEM_CONTEXT_TEMP_END();
+
+ FUNCTION_DEBUG_RESULT(KEY_VALUE, result);
+}
diff --git a/src/common/type/json.h b/src/common/type/json.h
new file mode 100644
index 000000000..3c1b69404
--- /dev/null
+++ b/src/common/type/json.h
@@ -0,0 +1,14 @@
+/***********************************************************************************************************************************
+Convert JSON to/from KeyValue
+***********************************************************************************************************************************/
+#ifndef COMMON_TYPE_JSON_H
+#define COMMON_TYPE_JSON_H
+
+#include "common/type/keyValue.h"
+
+/***********************************************************************************************************************************
+Functions
+***********************************************************************************************************************************/
+KeyValue *jsonToKv(const String *json);
+
+#endif
diff --git a/src/perl/embed.auto.c b/src/perl/embed.auto.c
index 2ec645f4c..0eac3b5f0 100644
--- a/src/perl/embed.auto.c
+++ b/src/perl/embed.auto.c
@@ -5046,6 +5046,8 @@ static const EmbeddedModule embeddedModule[] =
"push @EXPORT, qw(ERROR_PATH_CLOSE);\n"
"use constant ERROR_FILE_INFO => 98;\n"
"push @EXPORT, qw(ERROR_FILE_INFO);\n"
+ "use constant ERROR_JSON_FORMAT => 99;\n"
+ "push @EXPORT, qw(ERROR_JSON_FORMAT);\n"
"use constant ERROR_RUNTIME => 122;\n"
"push @EXPORT, qw(ERROR_RUNTIME);\n"
"use constant ERROR_INVALID => 123;\n"
diff --git a/test/define.yaml b/test/define.yaml
index 1d4559e27..2e8d965f8 100644
--- a/test/define.yaml
+++ b/test/define.yaml
@@ -185,6 +185,13 @@ unit:
coverage:
common/type/variantList: full
+ # ----------------------------------------------------------------------------------------------------------------------------
+ - name: type-json
+ total: 1
+
+ coverage:
+ common/type/json: full
+
# ----------------------------------------------------------------------------------------------------------------------------
- name: type-key-value
total: 2
diff --git a/test/src/module/common/typeJsonTest.c b/test/src/module/common/typeJsonTest.c
new file mode 100644
index 000000000..186c4d0cf
--- /dev/null
+++ b/test/src/module/common/typeJsonTest.c
@@ -0,0 +1,50 @@
+/***********************************************************************************************************************************
+Test Convert JSON to/from KeyValue
+***********************************************************************************************************************************/
+
+/***********************************************************************************************************************************
+Test Run
+***********************************************************************************************************************************/
+void
+testRun(void)
+{
+ FUNCTION_HARNESS_VOID();
+
+ // *****************************************************************************************************************************
+ if (testBegin("jsonToKv()"))
+ {
+ TEST_ERROR(jsonToKv(strNew("[")), JsonFormatError, "arrays not supported");
+ TEST_ERROR(jsonToKv(strNew("<")), JsonFormatError, "expected '{' but found '<'");
+ TEST_ERROR(jsonToKv(strNew("{>")), JsonFormatError, "expected '\"' but found '>'");
+ TEST_ERROR(jsonToKv(strNew("{\"\"")), JsonFormatError, "zero-length key not allowed");
+ TEST_ERROR(jsonToKv(strNew("{\"key1\"")), JsonFormatError, "expected ':' but found '");
+ TEST_ERROR(jsonToKv(strNew("{\"key1'")), JsonFormatError, "expected '\"' but found '''");
+ TEST_ERROR(jsonToKv(strNew("{\"key1\":t")), JsonFormatError, "unknown value type");
+ TEST_ERROR(jsonToKv(strNew("{\"key1\":123")), JsonFormatError, "expected '}' but found '3'");
+ TEST_ERROR(jsonToKv(strNew("{\"key1\":123>")), JsonFormatError, "expected '}' but found '>'");
+ TEST_ERROR(jsonToKv(strNew("{\"key1\":\"123'}")), JsonFormatError, "expected '\"' but found '}'");
+
+ // -------------------------------------------------------------------------------------------------------------------------
+ KeyValue *kv = NULL;
+
+ TEST_ASSIGN(kv, jsonToKv(strNew("{\"key1\":123}")), "single integer value");
+ TEST_RESULT_UINT(varUInt64(kvGet(kv, varNewStr(strNew("key1")))), 123, " check integer");
+
+ TEST_ASSIGN(kv, jsonToKv(strNew("{\"key1\":\"value1\"}")), "single string value");
+ TEST_RESULT_STR(strPtr(varStr(kvGet(kv, varNewStr(strNew("key1"))))), "value1", " check string");
+
+ TEST_ASSIGN(
+ kv,
+ jsonToKv(
+ strNew(
+ "{\"db-catalog-version\":201409291,\"db-control-version\":942,\"db-system-id\":6116111691796124355,"
+ "\"db-version\":\"9.4\"}")),
+ "multiple values");
+ TEST_RESULT_UINT(varUInt64(kvGet(kv, varNewStr(strNew("db-catalog-version")))), 201409291, " check integer");
+ TEST_RESULT_UINT(varUInt64(kvGet(kv, varNewStr(strNew("db-control-version")))), 942, " check integer");
+ TEST_RESULT_UINT(varUInt64(kvGet(kv, varNewStr(strNew("db-system-id")))), 6116111691796124355, " check integer");
+ TEST_RESULT_STR(strPtr(varStr(kvGet(kv, varNewStr(strNew("db-version"))))), "9.4", " check string");
+ }
+
+ FUNCTION_HARNESS_RESULT_VOID();
+}