Skip to content

Commit

Permalink
utility: new utility method to convert proto value to string (#36334)
Browse files Browse the repository at this point in the history
Commit Message: utility: new utility method to convert proto value to
string
Additional Description:

New utility method to convert the proto value to json. This could work
even the `ENVOY_ENABLE_YAML` is not set and is exception free.

Risk Level: low.
Testing: unit test.
Docs Changes: n/a.
Release Notes: n/a.
Platform Specific Features: n/a.

---------

Signed-off-by: wangbaiping <[email protected]>
  • Loading branch information
wbpcode authored Oct 7, 2024
1 parent 7bf87fd commit 68b4559
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 0 deletions.
10 changes: 10 additions & 0 deletions source/common/json/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,13 @@ envoy_cc_library(
"@com_google_absl//absl/strings",
],
)

envoy_cc_library(
name = "json_utility_lib",
srcs = ["json_utility.cc"],
hdrs = ["json_utility.h"],
deps = [
":json_streamer_lib",
"//source/common/protobuf:utility_lib_header",
],
)
5 changes: 5 additions & 0 deletions source/common/json/json_streamer.h
Original file line number Diff line number Diff line change
Expand Up @@ -456,5 +456,10 @@ template <class OutputBufferType> class StreamerBase {
*/
using BufferStreamer = StreamerBase<BufferOutput>;

/**
* A Streamer that streams to a string.
*/
using StringStreamer = StreamerBase<StringOutput>;

} // namespace Json
} // namespace Envoy
88 changes: 88 additions & 0 deletions source/common/json/json_utility.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include "source/common/json/json_utility.h"

namespace Envoy {
namespace Json {

namespace {

void structValueToJson(const ProtobufWkt::Struct& struct_value, StringStreamer::Map& level);
void listValueToJson(const ProtobufWkt::ListValue& list_value, StringStreamer::Array& level);

void valueToJson(const ProtobufWkt::Value& value, StringStreamer::Level& level) {
switch (value.kind_case()) {
case ProtobufWkt::Value::KIND_NOT_SET:
case ProtobufWkt::Value::kNullValue:
level.addNull();
break;
case ProtobufWkt::Value::kNumberValue:
level.addNumber(value.number_value());
break;
case ProtobufWkt::Value::kStringValue:
level.addString(value.string_value());
break;
case ProtobufWkt::Value::kBoolValue:
level.addBool(value.bool_value());
break;
case ProtobufWkt::Value::kStructValue:
structValueToJson(value.struct_value(), *level.addMap());
break;
case ProtobufWkt::Value::kListValue:
listValueToJson(value.list_value(), *level.addArray());
break;
}
}

void structValueToJson(const ProtobufWkt::Struct& struct_value, StringStreamer::Map& map) {
using PairRefWrapper =
std::reference_wrapper<const Protobuf::Map<std::string, ProtobufWkt::Value>::value_type>;
absl::InlinedVector<PairRefWrapper, 8> sorted_fields;
sorted_fields.reserve(struct_value.fields_size());

for (const auto& field : struct_value.fields()) {
sorted_fields.emplace_back(field);
}
// Sort the keys to make the output deterministic.
std::sort(sorted_fields.begin(), sorted_fields.end(),
[](PairRefWrapper a, PairRefWrapper b) { return a.get().first < b.get().first; });

for (const PairRefWrapper field : sorted_fields) {
map.addKey(field.get().first);
valueToJson(field.get().second, map);
}
}

void listValueToJson(const ProtobufWkt::ListValue& list_value, StringStreamer::Array& arr) {
for (const ProtobufWkt::Value& value : list_value.values()) {
valueToJson(value, arr);
}
}

} // namespace

void Utility::appendValueToString(const ProtobufWkt::Value& value, std::string& dest) {
StringStreamer streamer(dest);
switch (value.kind_case()) {
case ProtobufWkt::Value::KIND_NOT_SET:
case ProtobufWkt::Value::kNullValue:
streamer.addNull();
break;
case ProtobufWkt::Value::kNumberValue:
streamer.addNumber(value.number_value());
break;
case ProtobufWkt::Value::kStringValue:
streamer.addString(value.string_value());
break;
case ProtobufWkt::Value::kBoolValue:
streamer.addBool(value.bool_value());
break;
case ProtobufWkt::Value::kStructValue:
structValueToJson(value.struct_value(), *streamer.makeRootMap());
break;
case ProtobufWkt::Value::kListValue:
listValueToJson(value.list_value(), *streamer.makeRootArray());
break;
}
}

} // namespace Json
} // namespace Envoy
22 changes: 22 additions & 0 deletions source/common/json/json_utility.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include <string>

#include "source/common/json/json_streamer.h"
#include "source/common/protobuf/protobuf.h"

namespace Envoy {
namespace Json {

class Utility {
public:
/**
* Convert a ProtobufWkt::Value to a JSON string.
* @param value message of type type.googleapis.com/google.protobuf.Value
* @param dest JSON string.
*/
static void appendValueToString(const ProtobufWkt::Value& value, std::string& dest);
};

} // namespace Json
} // namespace Envoy
9 changes: 9 additions & 0 deletions test/common/json/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ envoy_cc_test(
],
)

envoy_cc_test(
name = "json_utility_test",
srcs = ["json_utility_test.cc"],
deps = [
"//source/common/json:json_utility_lib",
"//source/common/protobuf:utility_lib",
],
)

envoy_cc_test_binary(
name = "gen_excluded_unicodes",
srcs = ["gen_excluded_unicodes.cc"],
Expand Down
88 changes: 88 additions & 0 deletions test/common/json/json_utility_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include "source/common/json/json_utility.h"
#include "source/common/protobuf/message_validator_impl.h"
#include "source/common/protobuf/utility.h"

#include "gtest/gtest.h"

namespace Envoy {
namespace Json {
namespace {

std::string toJson(const ProtobufWkt::Value& v) {
std::string json_string;
Utility::appendValueToString(v, json_string);
return json_string;
}

TEST(JsonUtilityTest, AppendValueToString) {
ProtobufWkt::Value v;

// null
EXPECT_EQ(toJson(v), "null");

v.set_null_value(ProtobufWkt::NULL_VALUE);
EXPECT_EQ(toJson(v), "null");

// bool
v.set_bool_value(true);
EXPECT_EQ(toJson(v), "true");

v.set_bool_value(false);
EXPECT_EQ(toJson(v), "false");

// number
v.set_number_value(1);
EXPECT_EQ(toJson(v), "1");

v.set_number_value(1.1);
EXPECT_EQ(toJson(v), "1.1");

// string
v.set_string_value("foo");
EXPECT_EQ(toJson(v), "\"foo\"");

// struct
auto* struct_value = v.mutable_struct_value();
EXPECT_EQ(toJson(v), R"EOF({})EOF");

struct_value->mutable_fields()->insert({"foo", ValueUtil::stringValue("bar")});
EXPECT_EQ(toJson(v), R"EOF({"foo":"bar"})EOF");

// list
auto* list_value = v.mutable_list_value();

EXPECT_EQ(toJson(v), R"EOF([])EOF");

list_value->add_values()->set_string_value("foo");
list_value->add_values()->set_string_value("bar");

EXPECT_EQ(toJson(v), R"EOF(["foo","bar"])EOF");

// Complex structure
const std::string yaml = R"EOF(
a:
a:
- a: 1
b: 2
b:
- a: 3
b: 4
- a: 5
c: true
d: [5, 3.14]
e: foo
f: 1.1
b: [1, 2, 3]
c: bar
)EOF";

MessageUtil::loadFromYaml(yaml, v, ProtobufMessage::getNullValidationVisitor());

EXPECT_EQ(
toJson(v),
R"EOF({"a":{"a":[{"a":1,"b":2}],"b":[{"a":3,"b":4},{"a":5}],"c":true,"d":[5,"3.14"],"e":"foo","f":"1.1"},"b":[1,2,3],"c":"bar"})EOF");
}

} // namespace
} // namespace Json
} // namespace Envoy

0 comments on commit 68b4559

Please sign in to comment.