From dd712db85f3f196ecc9d7d80125079dbcb8e0492 Mon Sep 17 00:00:00 2001 From: ibireme Date: Tue, 12 Dec 2023 00:42:37 +0800 Subject: [PATCH] Add `YYJSON_WRITE_NEWLINE_AT_END` flag for JSON writer: #147 --- CHANGELOG.md | 5 +++ doc/API.md | 4 +++ src/yyjson.c | 35 ++++++++++++++++++--- src/yyjson.h | 4 +++ test/test_json_writer.c | 67 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e16524..1bd7e5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. +## Unreleased +#### Added +- Add `YYJSON_WRITE_NEWLINE_AT_END` flag for JSON writer: #147 + + ## 0.8.0 (2023-09-13) #### Added - Add `YYJSON_SUBTYPE_NOESC` subtype to mark strings that do not need to be escaped. diff --git a/doc/API.md b/doc/API.md index 1f71b41..d22892c 100644 --- a/doc/API.md +++ b/doc/API.md @@ -515,6 +515,10 @@ Invalid characters within string values will be copied byte by byte. If `YYJSON_ This flag does not affect the performance of correctly encoded string. +● **YYJSON_WRITE_NEWLINE_AT_END**
+Adds a newline character `\n` at the end of the JSON. +This can be helpful for text editors or NDJSON. + --------------- # Accessing JSON Document diff --git a/src/yyjson.c b/src/yyjson.c index 3e74166..9e4ad62 100644 --- a/src/yyjson.c +++ b/src/yyjson.c @@ -8309,13 +8309,15 @@ static_inline u8 *yyjson_write_single(yyjson_val *val, bool cpy = (enc_table == enc_table_cpy); bool esc = has_write_flag(ESCAPE_UNICODE) != 0; bool inv = has_write_flag(ALLOW_INVALID_UNICODE) != 0; + bool newline = has_write_flag(NEWLINE_AT_END) != 0; + const usize end_len = 2; /* '\n' and '\0' */ switch (unsafe_yyjson_get_type(val)) { case YYJSON_TYPE_RAW: str_len = unsafe_yyjson_get_len(val); str_ptr = (const u8 *)unsafe_yyjson_get_str(val); check_str_len(str_len); - incr_len(str_len + 1); + incr_len(str_len + end_len); cur = write_raw(cur, str_ptr, str_len); break; @@ -8323,7 +8325,7 @@ static_inline u8 *yyjson_write_single(yyjson_val *val, str_len = unsafe_yyjson_get_len(val); str_ptr = (const u8 *)unsafe_yyjson_get_str(val); check_str_len(str_len); - incr_len(str_len * 6 + 4); + incr_len(str_len * 6 + 2 + end_len); if (likely(cpy) && unsafe_yyjson_get_subtype(val)) { cur = write_string_noesc(cur, str_ptr, str_len); } else { @@ -8333,7 +8335,7 @@ static_inline u8 *yyjson_write_single(yyjson_val *val, break; case YYJSON_TYPE_NUM: - incr_len(32); + incr_len(32 + end_len); cur = write_number(cur, val, flg); if (unlikely(!cur)) goto fail_num; break; @@ -8349,13 +8351,13 @@ static_inline u8 *yyjson_write_single(yyjson_val *val, break; case YYJSON_TYPE_ARR: - incr_len(4); + incr_len(2 + end_len); byte_copy_2(cur, "[]"); cur += 2; break; case YYJSON_TYPE_OBJ: - incr_len(4); + incr_len(2 + end_len); byte_copy_2(cur, "{}"); cur += 2; break; @@ -8364,6 +8366,7 @@ static_inline u8 *yyjson_write_single(yyjson_val *val, goto fail_type; } + if (newline) *cur++ = '\n'; *cur = '\0'; *dat_len = (usize)(cur - hdr); memset(err, 0, sizeof(yyjson_write_err)); @@ -8436,6 +8439,7 @@ static_inline u8 *yyjson_write_minify(const yyjson_val *root, bool cpy = (enc_table == enc_table_cpy); bool esc = has_write_flag(ESCAPE_UNICODE) != 0; bool inv = has_write_flag(ALLOW_INVALID_UNICODE) != 0; + bool newline = has_write_flag(NEWLINE_AT_END) != 0; alc_len = root->uni.ofs / sizeof(yyjson_val); alc_len = alc_len * YYJSON_WRITER_ESTIMATED_MINIFY_RATIO + 64; @@ -8542,6 +8546,11 @@ static_inline u8 *yyjson_write_minify(const yyjson_val *root, } doc_end: + if (newline) { + incr_len(2); + *(cur - 1) = '\n'; + cur++; + } *--cur = '\0'; *dat_len = (usize)(cur - hdr); memset(err, 0, sizeof(yyjson_write_err)); @@ -8615,6 +8624,7 @@ static_inline u8 *yyjson_write_pretty(const yyjson_val *root, bool esc = has_write_flag(ESCAPE_UNICODE) != 0; bool inv = has_write_flag(ALLOW_INVALID_UNICODE) != 0; usize spaces = has_write_flag(PRETTY_TWO_SPACES) ? 2 : 4; + bool newline = has_write_flag(NEWLINE_AT_END) != 0; alc_len = root->uni.ofs / sizeof(yyjson_val); alc_len = alc_len * YYJSON_WRITER_ESTIMATED_PRETTY_RATIO + 64; @@ -8745,6 +8755,10 @@ static_inline u8 *yyjson_write_pretty(const yyjson_val *root, } doc_end: + if (newline) { + incr_len(2); + *cur++ = '\n'; + } *cur = '\0'; *dat_len = (usize)(cur - hdr); memset(err, 0, sizeof(yyjson_write_err)); @@ -8977,6 +8991,7 @@ static_inline u8 *yyjson_mut_write_minify(const yyjson_mut_val *root, bool cpy = (enc_table == enc_table_cpy); bool esc = has_write_flag(ESCAPE_UNICODE) != 0; bool inv = has_write_flag(ALLOW_INVALID_UNICODE) != 0; + bool newline = has_write_flag(NEWLINE_AT_END) != 0; alc_len = estimated_val_num * YYJSON_WRITER_ESTIMATED_MINIFY_RATIO + 64; alc_len = size_align_up(alc_len, sizeof(yyjson_mut_write_ctx)); @@ -9087,6 +9102,11 @@ static_inline u8 *yyjson_mut_write_minify(const yyjson_mut_val *root, } doc_end: + if (newline) { + incr_len(2); + *(cur - 1) = '\n'; + cur++; + } *--cur = '\0'; *dat_len = (usize)(cur - hdr); err->code = YYJSON_WRITE_SUCCESS; @@ -9162,6 +9182,7 @@ static_inline u8 *yyjson_mut_write_pretty(const yyjson_mut_val *root, bool esc = has_write_flag(ESCAPE_UNICODE) != 0; bool inv = has_write_flag(ALLOW_INVALID_UNICODE) != 0; usize spaces = has_write_flag(PRETTY_TWO_SPACES) ? 2 : 4; + bool newline = has_write_flag(NEWLINE_AT_END) != 0; alc_len = estimated_val_num * YYJSON_WRITER_ESTIMATED_PRETTY_RATIO + 64; alc_len = size_align_up(alc_len, sizeof(yyjson_mut_write_ctx)); @@ -9296,6 +9317,10 @@ static_inline u8 *yyjson_mut_write_pretty(const yyjson_mut_val *root, } doc_end: + if (newline) { + incr_len(2); + *cur++ = '\n'; + } *cur = '\0'; *dat_len = (usize)(cur - hdr); err->code = YYJSON_WRITE_SUCCESS; diff --git a/src/yyjson.h b/src/yyjson.h index 363d3f3..7d58993 100644 --- a/src/yyjson.h +++ b/src/yyjson.h @@ -1096,6 +1096,10 @@ static const yyjson_write_flag YYJSON_WRITE_ALLOW_INVALID_UNICODE = 1 << 5; This flag will override `YYJSON_WRITE_PRETTY` flag. */ static const yyjson_write_flag YYJSON_WRITE_PRETTY_TWO_SPACES = 1 << 6; +/** Adds a newline character `\n` at the end of the JSON. + This can be helpful for text editors or NDJSON. */ +static const yyjson_write_flag YYJSON_WRITE_NEWLINE_AT_END = 1 << 7; + /** Result code for JSON writer */ diff --git a/test/test_json_writer.c b/test/test_json_writer.c index 523cfc9..a34997e 100644 --- a/test/test_json_writer.c +++ b/test/test_json_writer.c @@ -873,6 +873,73 @@ yy_test_case(test_json_writer) { yyjson_doc_free(doc); yyjson_mut_doc_free(mdoc); } + + + // test newline at end + { + size_t len; + const char *str; + yyjson_doc *doc; + yyjson_mut_doc *mdoc; + char *ret; + + // single value + str = "123"; + doc = yyjson_read(str, strlen(str), 0); + ret = yyjson_write(doc, YYJSON_WRITE_NEWLINE_AT_END, &len); + yy_assert(strlen(ret) == len && len == strlen(str) + 1); + yy_assert(memcmp(str, ret, strlen(str)) == 0); + yy_assert(ret[strlen(str)] == '\n'); + free(ret); + + mdoc = yyjson_doc_mut_copy(doc, NULL); + ret = yyjson_mut_write(mdoc, YYJSON_WRITE_NEWLINE_AT_END, &len); + yy_assert(strlen(ret) == len && len == strlen(str) + 1); + yy_assert(memcmp(str, ret, strlen(str)) == 0); + yy_assert(ret[strlen(str)] == '\n'); + free(ret); + + yyjson_doc_free(doc); + yyjson_mut_doc_free(mdoc); + + // multiple values + str = "[123]"; + doc = yyjson_read(str, strlen(str), 0); + ret = yyjson_write(doc, YYJSON_WRITE_NEWLINE_AT_END, &len); + yy_assert(strlen(ret) == len && len == strlen(str) + 1); + yy_assert(memcmp(str, ret, strlen(str)) == 0); + yy_assert(ret[strlen(str)] == '\n'); + free(ret); + + mdoc = yyjson_doc_mut_copy(doc, NULL); + ret = yyjson_mut_write(mdoc, YYJSON_WRITE_NEWLINE_AT_END, &len); + yy_assert(strlen(ret) == len && len == strlen(str) + 1); + yy_assert(memcmp(str, ret, strlen(str)) == 0); + yy_assert(ret[strlen(str)] == '\n'); + free(ret); + + yyjson_doc_free(doc); + yyjson_mut_doc_free(mdoc); + + // multiple values, pretty + str = "[\n 123\n]"; + doc = yyjson_read(str, strlen(str), 0); + ret = yyjson_write(doc, YYJSON_WRITE_PRETTY | YYJSON_WRITE_NEWLINE_AT_END, &len); + yy_assert(strlen(ret) == len && len == strlen(str) + 1); + yy_assert(memcmp(str, ret, strlen(str)) == 0); + yy_assert(ret[strlen(str)] == '\n'); + free(ret); + + mdoc = yyjson_doc_mut_copy(doc, NULL); + ret = yyjson_mut_write(mdoc, YYJSON_WRITE_PRETTY | YYJSON_WRITE_NEWLINE_AT_END, &len); + yy_assert(strlen(ret) == len && len == strlen(str) + 1); + yy_assert(memcmp(str, ret, strlen(str)) == 0); + yy_assert(ret[strlen(str)] == '\n'); + free(ret); + + yyjson_doc_free(doc); + yyjson_mut_doc_free(mdoc); + } #endif // test build JSON on stack