blob: 7494b11fd9a9914ff9b4b333bfe8ee31e66b664b [file] [edit]
// 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;
}