mirror of
https://github.com/open62541/open62541.git
synced 2025-06-03 04:00:21 +00:00
feat(core): Add pretty-printing for JSON
This commit is contained in:
parent
cbef43e49f
commit
39e430368c
@ -1213,9 +1213,6 @@ UA_decodeBinary(const UA_ByteString *inBuf,
|
||||
* JSON En/Decoding
|
||||
* ----------------
|
||||
*
|
||||
* The JSON encoding always produces an encoding that is compatible with the OPC
|
||||
* UA specification.
|
||||
*
|
||||
* The JSON decoding can parse the official encoding from the OPC UA
|
||||
* specification. It further allows the following extensions:
|
||||
*
|
||||
@ -1240,6 +1237,14 @@ typedef struct {
|
||||
const UA_String *serverUris;
|
||||
size_t serverUrisSize;
|
||||
UA_Boolean useReversible;
|
||||
|
||||
UA_Boolean prettyPrint; /* Add newlines and spaces for legibility */
|
||||
|
||||
/* Enabling the following options leads to non-standard compatible JSON5
|
||||
* encoding! Use it for pretty-printing, but not for sending messages over
|
||||
* the network. (Our own decoding can still parse it.) */
|
||||
|
||||
UA_Boolean unquotedKeys; /* Don't print quotes around object element keys */
|
||||
} UA_EncodeJsonOptions;
|
||||
|
||||
/* Returns the number of bytes the value src takes in json encoding. Returns
|
||||
|
@ -197,7 +197,7 @@ UA_NetworkMessage_encodeJson_internal(const UA_NetworkMessage* src, CtxJson *ctx
|
||||
const UA_DataSetMessage *dataSetMessages =
|
||||
src->payload.dataSetPayload.dataSetMessages;
|
||||
for(UA_UInt16 i = 0; i < count; i++) {
|
||||
writeJsonCommaIfNeeded(ctx);
|
||||
rv |= writeJsonBeforeElement(ctx, true);
|
||||
rv |= UA_DataSetMessage_encodeJson_internal(&dataSetMessages[i],
|
||||
dataSetWriterIds[i], ctx);
|
||||
if(rv != UA_STATUSCODE_GOOD)
|
||||
|
@ -86,6 +86,28 @@ static WRITE_JSON_ELEMENT(Quote) {
|
||||
return writeChar(ctx, '\"');
|
||||
}
|
||||
|
||||
UA_StatusCode
|
||||
writeJsonBeforeElement(CtxJson *ctx, UA_Boolean distinct) {
|
||||
/* Comma if needed */
|
||||
UA_StatusCode res = UA_STATUSCODE_GOOD;
|
||||
if(ctx->commaNeeded[ctx->depth])
|
||||
res |= writeChar(ctx, ',');
|
||||
|
||||
if(ctx->prettyPrint) {
|
||||
if(distinct) {
|
||||
/* Newline and indent if needed */
|
||||
res |= writeChar(ctx, '\n');
|
||||
for(size_t i = 0; i < ctx->depth; i++)
|
||||
res |= writeChar(ctx, '\t');
|
||||
} else if(ctx->commaNeeded[ctx->depth]) {
|
||||
/* Space after the comma if no newline */
|
||||
res |= writeChar(ctx, ' ');
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
WRITE_JSON_ELEMENT(ObjStart) {
|
||||
/* increase depth, save: before first key-value no comma needed. */
|
||||
if(ctx->depth >= UA_JSON_ENCODING_MAX_RECURSION - 1)
|
||||
@ -98,9 +120,19 @@ WRITE_JSON_ELEMENT(ObjStart) {
|
||||
WRITE_JSON_ELEMENT(ObjEnd) {
|
||||
if(ctx->depth == 0)
|
||||
return UA_STATUSCODE_BADENCODINGERROR;
|
||||
|
||||
UA_Boolean have_elem = ctx->commaNeeded[ctx->depth];
|
||||
ctx->depth--;
|
||||
ctx->commaNeeded[ctx->depth] = true;
|
||||
return writeChar(ctx, '}');
|
||||
|
||||
UA_StatusCode res = UA_STATUSCODE_GOOD;
|
||||
if(ctx->prettyPrint && have_elem) {
|
||||
res |= writeChar(ctx, '\n');
|
||||
for(size_t i = 0; i < ctx->depth; i++)
|
||||
res |= writeChar(ctx, '\t');
|
||||
}
|
||||
res |= writeChar(ctx, '}');
|
||||
return res;
|
||||
}
|
||||
|
||||
WRITE_JSON_ELEMENT(ArrStart) {
|
||||
@ -115,21 +147,24 @@ WRITE_JSON_ELEMENT(ArrStart) {
|
||||
WRITE_JSON_ELEMENT(ArrEnd) {
|
||||
if(ctx->depth == 0)
|
||||
return UA_STATUSCODE_BADENCODINGERROR;
|
||||
UA_Boolean have_elem = ctx->commaNeeded[ctx->depth];
|
||||
ctx->depth--;
|
||||
ctx->commaNeeded[ctx->depth] = true;
|
||||
return writeChar(ctx, ']');
|
||||
}
|
||||
|
||||
WRITE_JSON_ELEMENT(CommaIfNeeded) {
|
||||
if(ctx->commaNeeded[ctx->depth])
|
||||
return writeChar(ctx, ',');
|
||||
return UA_STATUSCODE_GOOD;
|
||||
UA_StatusCode res = UA_STATUSCODE_GOOD;
|
||||
if(ctx->prettyPrint && have_elem) {
|
||||
res |= writeChar(ctx, '\n');
|
||||
for(size_t i = 0; i < ctx->depth; i++)
|
||||
res |= writeChar(ctx, '\t');
|
||||
}
|
||||
res |= writeChar(ctx, ']');
|
||||
return res;
|
||||
}
|
||||
|
||||
status
|
||||
writeJsonArrElm(CtxJson *ctx, const void *value,
|
||||
const UA_DataType *type) {
|
||||
status ret = writeJsonCommaIfNeeded(ctx);
|
||||
UA_Boolean distinct = (type->typeKind > UA_DATATYPEKIND_DOUBLE);
|
||||
status ret = writeJsonBeforeElement(ctx, distinct);
|
||||
ctx->commaNeeded[ctx->depth] = true;
|
||||
ret |= encodeJsonInternal(value, type, ctx);
|
||||
return ret;
|
||||
@ -207,23 +242,17 @@ static const char* UA_JSONKEY_INNERDIAGNOSTICINFO = "InnerDiagnosticInfo";
|
||||
* comma in front of key if needed. Encapsulates key in quotes. */
|
||||
status UA_FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
writeJsonKey(CtxJson *ctx, const char* key) {
|
||||
size_t size = strlen(key);
|
||||
if(ctx->pos + size + 4 > ctx->end) /* +4 because of " " : and , */
|
||||
return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED;
|
||||
status ret = writeJsonCommaIfNeeded(ctx);
|
||||
status ret = writeJsonBeforeElement(ctx, true);
|
||||
ctx->commaNeeded[ctx->depth] = true;
|
||||
if(ctx->calcOnly) {
|
||||
ctx->pos += 3;
|
||||
ctx->pos += size;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret |= writeChar(ctx, '\"');
|
||||
for(size_t i = 0; i < size; i++) {
|
||||
*(ctx->pos++) = (u8)key[i];
|
||||
}
|
||||
ret |= writeChar(ctx, '\"');
|
||||
ret |= writeChar(ctx, ':');
|
||||
if(!ctx->unquotedKeys)
|
||||
ret |= writeChar(ctx, '\"');
|
||||
ret |= writeChars(ctx, key, strlen(key));
|
||||
if(!ctx->unquotedKeys)
|
||||
ret |= writeChar(ctx, '\"');
|
||||
if(!ctx->unquotedKeys)
|
||||
ret |= writeChar(ctx, ':');
|
||||
if(ctx->prettyPrint)
|
||||
ret |= writeChar(ctx, ' ');
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -450,8 +479,9 @@ encodeJsonArray(CtxJson *ctx, const void *ptr, size_t length,
|
||||
|
||||
uintptr_t uptr = (uintptr_t)ptr;
|
||||
encodeJsonSignature encodeType = encodeJsonJumpTable[type->typeKind];
|
||||
UA_Boolean distinct = (type->typeKind > UA_DATATYPEKIND_DOUBLE);
|
||||
for(size_t i = 0; i < length && ret == UA_STATUSCODE_GOOD; ++i) {
|
||||
ret |= writeJsonCommaIfNeeded(ctx);
|
||||
ret |= writeJsonBeforeElement(ctx, distinct);
|
||||
if(isNull((const void*)uptr, type))
|
||||
ret |= writeJsonNull(ctx); /* null values are written as "null" */
|
||||
else
|
||||
@ -665,9 +695,8 @@ ENCODE_JSON(Guid) {
|
||||
if(ctx->pos + 38 > ctx->end) /* 36 + 2 (") */
|
||||
return UA_STATUSCODE_BADENCODINGLIMITSEXCEEDED;
|
||||
status ret = writeJsonQuote(ctx);
|
||||
u8 *buf = ctx->pos;
|
||||
if(!ctx->calcOnly)
|
||||
UA_Guid_to_hex(src, buf);
|
||||
UA_Guid_to_hex(src, ctx->pos, false);
|
||||
ctx->pos += 36;
|
||||
ret |= writeJsonQuote(ctx);
|
||||
return ret;
|
||||
@ -1035,7 +1064,7 @@ addMultiArrayContentJSON(CtxJson *ctx, void* array, const UA_DataType *type,
|
||||
if(ret != UA_STATUSCODE_GOOD)
|
||||
return ret;
|
||||
for(size_t i = 0; i < arrayDimensions[dimensionIndex]; i++) {
|
||||
ret |= writeJsonCommaIfNeeded(ctx);
|
||||
ret |= writeJsonBeforeElement(ctx, true);
|
||||
ret |= addMultiArrayContentJSON(ctx, array, type, index, arrayDimensions,
|
||||
dimensionIndex + 1, dimensionSize);
|
||||
ctx->commaNeeded[ctx->depth] = true;
|
||||
@ -1279,38 +1308,12 @@ encodeJsonInternal(const void *src, const UA_DataType *type, CtxJson *ctx) {
|
||||
return encodeJsonJumpTable[type->typeKind](src, type, ctx);
|
||||
}
|
||||
|
||||
static status
|
||||
UA_encodeJsonInternal(const void *src, const UA_DataType *type,
|
||||
u8 **bufPos, const u8 **bufEnd, const UA_String *namespaces,
|
||||
size_t namespaceSize, const UA_String *serverUris,
|
||||
size_t serverUriSize, UA_Boolean useReversible) {
|
||||
if(!src || !type)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
/* Set up the context */
|
||||
CtxJson ctx;
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
ctx.pos = *bufPos;
|
||||
ctx.end = *bufEnd;
|
||||
ctx.depth = 0;
|
||||
ctx.namespaces = namespaces;
|
||||
ctx.namespacesSize = namespaceSize;
|
||||
ctx.serverUris = serverUris;
|
||||
ctx.serverUrisSize = serverUriSize;
|
||||
ctx.useReversible = useReversible;
|
||||
ctx.calcOnly = false;
|
||||
|
||||
/* Encode */
|
||||
status ret = encodeJsonJumpTable[type->typeKind](src, type, &ctx);
|
||||
|
||||
*bufPos = ctx.pos;
|
||||
*bufEnd = ctx.end;
|
||||
return ret;
|
||||
}
|
||||
|
||||
UA_StatusCode
|
||||
UA_encodeJson(const void *src, const UA_DataType *type, UA_ByteString *outBuf,
|
||||
const UA_EncodeJsonOptions *options) {
|
||||
if(!src || !type)
|
||||
return UA_STATUSCODE_BADINTERNALERROR;
|
||||
|
||||
/* Allocate buffer */
|
||||
UA_Boolean allocated = false;
|
||||
status res = UA_STATUSCODE_GOOD;
|
||||
@ -1322,20 +1325,30 @@ UA_encodeJson(const void *src, const UA_DataType *type, UA_ByteString *outBuf,
|
||||
allocated = true;
|
||||
}
|
||||
|
||||
/* Encode */
|
||||
u8 *pos = outBuf->data;
|
||||
const u8 *posEnd = &outBuf->data[outBuf->length];
|
||||
/* Set up the context */
|
||||
CtxJson ctx;
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
ctx.pos = outBuf->data;
|
||||
ctx.end = &outBuf->data[outBuf->length];
|
||||
ctx.depth = 0;
|
||||
ctx.calcOnly = false;
|
||||
ctx.useReversible = true; /* default */
|
||||
if(options) {
|
||||
res = UA_encodeJsonInternal(src, type, &pos, &posEnd, options->namespaces,
|
||||
options->namespacesSize, options->serverUris,
|
||||
options->serverUrisSize, options->useReversible);
|
||||
} else {
|
||||
res = UA_encodeJsonInternal(src, type, &pos, &posEnd, NULL, 0u, NULL, 0u, true);
|
||||
ctx.namespaces = options->namespaces;
|
||||
ctx.namespacesSize = options->namespacesSize;
|
||||
ctx.serverUris = options->serverUris;
|
||||
ctx.serverUrisSize = options->serverUrisSize;
|
||||
ctx.useReversible = options->useReversible;
|
||||
ctx.prettyPrint = options->prettyPrint;
|
||||
ctx.unquotedKeys = options->unquotedKeys;
|
||||
}
|
||||
|
||||
/* Encode */
|
||||
res = encodeJsonJumpTable[type->typeKind](src, type, &ctx);
|
||||
|
||||
/* Clean up */
|
||||
if(res == UA_STATUSCODE_GOOD) {
|
||||
outBuf->length = (size_t)((uintptr_t)pos - (uintptr_t)outBuf->data);
|
||||
outBuf->length = (size_t)((uintptr_t)ctx.pos - (uintptr_t)outBuf->data);
|
||||
} else if(allocated) {
|
||||
UA_ByteString_clear(outBuf);
|
||||
}
|
||||
|
@ -36,6 +36,9 @@ typedef struct {
|
||||
|
||||
size_t serverUrisSize;
|
||||
const UA_String *serverUris;
|
||||
|
||||
UA_Boolean prettyPrint;
|
||||
UA_Boolean unquotedKeys;
|
||||
} CtxJson;
|
||||
|
||||
UA_StatusCode writeJsonObjStart(CtxJson *ctx);
|
||||
@ -49,9 +52,12 @@ UA_StatusCode writeJsonArrElm(CtxJson *ctx, const void *value,
|
||||
UA_StatusCode writeJsonArrEnd(CtxJson *ctx);
|
||||
|
||||
UA_StatusCode writeJsonKey(CtxJson *ctx, const char* key);
|
||||
UA_StatusCode writeJsonCommaIfNeeded(CtxJson *ctx);
|
||||
UA_StatusCode writeJsonNull(CtxJson *ctx);
|
||||
|
||||
/* Adds a comma if needed. Distinct elements go on a new line if pretty-printing
|
||||
* is enabled. */
|
||||
UA_StatusCode writeJsonBeforeElement(CtxJson *ctx, UA_Boolean distinct);
|
||||
|
||||
status
|
||||
encodeJsonInternal(const void *src, const UA_DataType *type, CtxJson *ctx);
|
||||
|
||||
|
@ -92,7 +92,7 @@ START_TEST(encodeShallYieldDecode) {
|
||||
ck_assert(UA_order(obj1, obj2, &UA_TYPES[_i]) == UA_ORDER_EQ);
|
||||
|
||||
// pretty-print the value
|
||||
#ifdef UA_ENABLE_TYPEDESCRIPTION
|
||||
#ifdef UA_ENABLE_JSON_ENCODING
|
||||
UA_Byte staticBuf[4096];
|
||||
UA_String buf;
|
||||
buf.data = staticBuf;
|
||||
|
Loading…
Reference in New Issue
Block a user