| // Copyright 2025 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "json_writer.h" |
| |
| #include <stdint.h> |
| |
| #include "json.h" |
| #include "util.h" |
| |
| namespace { |
| // static |
| void AppendBool(std::string& out, bool v) { |
| out += v ? "true" : "false"; |
| } |
| |
| // static |
| void AppendInt(std::string& out, int64_t v) { |
| StringAppendFormat(out, "%" PRId64, v); |
| } |
| |
| // static |
| void AppendFloat(std::string& out, double v) { |
| StringAppendFormat(out, "%g", v); |
| } |
| |
| // static |
| void AppendString(std::string& out, StringPiece v) { |
| out.push_back('"'); |
| EncodeAppendJSONString(&out, v); |
| out.push_back('"'); |
| } |
| |
| } // namespace |
| |
| JsonWriter::JsonWriter() {} |
| |
| JsonWriter::~JsonWriter() {} |
| |
| JsonWriter::List::~List() { |
| out_ += (count_ > 0) ? " ]" : "]"; |
| } |
| |
| void JsonWriter::List::AddBool(bool v) { |
| AddComma(); |
| AppendBool(out_, v); |
| } |
| |
| void JsonWriter::List::AddInt(int64_t v) { |
| AddComma(); |
| AppendInt(out_, v); |
| } |
| |
| void JsonWriter::List::AddFloat(double v) { |
| AddComma(); |
| AppendFloat(out_, v); |
| } |
| |
| void JsonWriter::List::AddString(StringPiece v) { |
| AddComma(); |
| AppendString(out_, v); |
| } |
| |
| void JsonWriter::List::AddRawJson(StringPiece v) { |
| AddComma(); |
| out_.append(v.begin(), v.size()); |
| } |
| |
| std::unique_ptr<JsonWriter::List> JsonWriter::List::AddList() { |
| AddComma(); |
| out_ += "["; |
| return std::make_unique<List>(out_); |
| } |
| |
| std::unique_ptr<JsonWriter::Dict> JsonWriter::List::AddDict() { |
| AddComma(); |
| out_ += "{"; |
| return std::make_unique<Dict>(out_); |
| } |
| |
| void JsonWriter::List::AddComma() { |
| if (++count_ == 1) { |
| out_.push_back(' '); |
| } else { |
| out_.append(", ", 2); |
| } |
| } |
| |
| JsonWriter::Dict::~Dict() { |
| out_ += (count_ > 0) ? " }" : "}"; |
| } |
| |
| void JsonWriter::Dict::AddBool(StringPiece key, bool v) { |
| AddKey(key); |
| AppendBool(out_, v); |
| } |
| |
| void JsonWriter::Dict::AddInt(StringPiece key, int64_t v) { |
| AddKey(key); |
| AppendInt(out_, v); |
| } |
| |
| void JsonWriter::Dict::AddFloat(StringPiece key, double v) { |
| AddKey(key); |
| AppendFloat(out_, v); |
| } |
| |
| void JsonWriter::Dict::AddString(StringPiece key, StringPiece v) { |
| AddKey(key); |
| AppendString(out_, v); |
| } |
| void JsonWriter::Dict::AddRawJson(StringPiece key, StringPiece v) { |
| AddKey(key); |
| out_.append(v.begin(), v.size()); |
| } |
| |
| std::unique_ptr<JsonWriter::List> JsonWriter::Dict::AddList(StringPiece key) { |
| AddKey(key); |
| out_ += "["; |
| return std::make_unique<List>(out_); |
| } |
| std::unique_ptr<JsonWriter::Dict> JsonWriter::Dict::AddDict(StringPiece key) { |
| AddKey(key); |
| out_ += "{"; |
| return std::make_unique<Dict>(out_); |
| } |
| |
| void JsonWriter::Dict::AddComma() { |
| if (++count_ == 1) { |
| out_.push_back(' '); |
| } else { |
| out_.append(", ", 2); |
| } |
| } |
| |
| void JsonWriter::Dict::AddKey(StringPiece key) { |
| AddComma(); |
| AppendString(out_, key); |
| out_ += ": "; |
| } |
| |
| std::string JsonWriter::Normalize(std::string input_json) { |
| std::string result; |
| |
| // Helper lambda to skip over a JSON string expression. |start| must point |
| // at the opening quote, and |end| must point to the end of the input. |
| // This returns the first character after the closing quote. In case of |
| // malformed string (i.e. missing closing quote), this returns |end|. |
| auto skip_json_string = [](const char* start, |
| const char* end) -> const char* { |
| // Skip the initial quote. |
| for (const char* p = start + 1; p < end; ++p) { |
| if (*p == '"') { |
| // Count the number of backslashes before this quote. If it |
| // is even, this is the closing quote. |
| const char* q = p; |
| while (q > start && q[-1] == '\\') |
| q--; |
| size_t count = (p - q); |
| if ((count % 2) == 0) { |
| return p + 1; |
| } |
| } |
| } |
| return end; |
| }; |
| |
| // First, remove any newline or space that does not belong to a string. |
| const char* start = input_json.c_str(); |
| const char* p = start; |
| const char* end = p + input_json.size(); |
| for (; p < end; ++p) { |
| char ch = *p; |
| if (ch == '"') { |
| const char* str_end = skip_json_string(p, end); |
| result.append(p, str_end - p); |
| p = str_end - 1; |
| continue; |
| } |
| if (ch == '\n' || ch == ' ') |
| continue; |
| result.push_back(ch); |
| } |
| |
| // Second, add spaces after certain symbols depending on context. |
| input_json = std::move(result); |
| start = input_json.c_str(); |
| end = start + input_json.size(); |
| for (p = start; p < end; ++p) { |
| char ch = *p; |
| char next_ch = (p + 1 < end) ? p[1] : '\0'; |
| |
| result.push_back(ch); |
| |
| switch (ch) { |
| case '[': |
| // Opening array followed by space unless the array is empty. |
| if (next_ch != ']') |
| result.push_back(' '); |
| break; |
| case '{': |
| // Opening object followed by a space unless the object is empty. |
| if (next_ch != '}') |
| result.push_back(' '); |
| break; |
| case '}': |
| case ']': |
| // A closing character not followed by a comma needs to be followed by a |
| // space, unless if it the last one from the input. |
| if (next_ch != ',' && p + 1 < end) |
| result.push_back(' '); |
| break; |
| case ':': |
| case ',': |
| // Colon or comma are always followed by a space. |
| result.push_back(' '); |
| break; |
| case '"': { |
| // A string that is not followed by a comma or colon needs a space after |
| // it. |
| const char* str_end = skip_json_string(p, end); |
| result.append(p + 1, str_end - p - 1); |
| next_ch = (str_end < end) ? str_end[0] : '\0'; |
| if (next_ch != ',' && next_ch != ':') |
| result.push_back(' '); |
| p = str_end - 1; |
| break; |
| } |
| default: |
| // A digit or alphanumerical character followed by a closing character |
| // needs a space. |
| if (next_ch == ']' || next_ch == '}') |
| result.push_back(' '); |
| } |
| } |
| return result; |
| } |