Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions serde_json/contrib/message_to_json.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include "contrib/message_to_json.h"

#include <string>

#include "serde_json_bridge.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "third_party/protobuf/json/json.h"
#include "third_party/protobuf/message.h"

namespace security::json::serde_json_bridge::contrib {

absl::StatusOr<SerdeJson> MessageToJson(
const proto2::Message& message, const proto2::json::PrintOptions& options) {
std::string json_string;
absl::Status status =
proto2::json::MessageToJsonString(message, &json_string, options);
if (!status.ok()) {
return status;
}
return SerdeJson::Parse(json_string);
}

absl::Status JsonToMessage(const SerdeJson& json, proto2::Message* message,
const proto2::json::ParseOptions& options) {
std::string json_string = json.ToString(/*sort_keys=*/false);
return proto2::json::JsonStringToMessage(json_string, message, options);
}

} // namespace security::json::serde_json_bridge::contrib
39 changes: 39 additions & 0 deletions serde_json/contrib/message_to_json.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#ifndef SECURITY_JSON_SERDE_JSON_CONTRIB_MESSAGE_TO_JSON_H_
#define SECURITY_JSON_SERDE_JSON_CONTRIB_MESSAGE_TO_JSON_H_

#include <concepts>

#include "serde_json_bridge.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "third_party/protobuf/json/json.h"
#include "third_party/protobuf/message.h"

namespace security::json::serde_json_bridge::contrib {

// Converts a proto message to a SerdeJson object.
absl::StatusOr<SerdeJson> MessageToJson(
const proto2::Message& message,
const proto2::json::PrintOptions& options = proto2::json::PrintOptions());

// Converts a SerdeJson object to a proto message.
absl::Status JsonToMessage(
const SerdeJson& json, proto2::Message* message,
const proto2::json::ParseOptions& options = proto2::json::ParseOptions());

// Converts a SerdeJson object to a proto message. This will construct a proto
// message to populate with the JSON data.
template <typename T>
requires std::derived_from<T, proto2::Message>
absl::StatusOr<T> JsonToMessage(
const SerdeJson& json,
const proto2::json::ParseOptions& options = proto2::json::ParseOptions()) {
T message;
absl::Status status = JsonToMessage(json, &message, options);
if (!status.ok()) return status;
return message;
}

} // namespace security::json::serde_json_bridge::contrib

#endif // SECURITY_JSON_SERDE_JSON_CONTRIB_MESSAGE_TO_JSON_H_
108 changes: 108 additions & 0 deletions serde_json/contrib/message_to_json_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#include "contrib/message_to_json.h"

#include "contrib/messages.proto.h"
#include "serde_json_bridge.h"
#include "testing/base/public/gmock.h"
#include "testing/base/public/gunit.h"
#include "third_party/protobuf/json/json.h"

namespace security::json::serde_json_bridge::contrib {
namespace {

TEST(MessageToJsonTest, SimpleMessageToJson) {
SimpleMessage message;
message.set_string_field("hello");
message.set_int32_field(123);
message.set_bool_field(true);
message.set_enum_field(SIMPLE_ENUM_A);

ASSERT_OK_AND_ASSIGN(SerdeJson json, MessageToJson(message));

EXPECT_EQ(
json.ToString(),
R"json({"boolField":true,"enumField":"SIMPLE_ENUM_A","int32Field":123,"stringField":"hello"})json");
}

TEST(MessageToJsonTest, SimpleMessageToJsonAlwaysPrintEnumsAsInts) {
SimpleMessage message;
message.set_enum_field(SIMPLE_ENUM_A);

proto2::json::PrintOptions options;
options.always_print_enums_as_ints = true;

ASSERT_OK_AND_ASSIGN(SerdeJson json, MessageToJson(message, options));

EXPECT_EQ(json.ToString(), R"json({"enumField":1})json");
}

TEST(MessageToJsonTest, SimpleMessageRoundTrip) {
SimpleMessage message;
message.set_string_field("hello");
message.set_int32_field(123);
message.set_bool_field(true);
message.set_enum_field(SIMPLE_ENUM_B);

proto2::json::PrintOptions options;

options.preserve_proto_field_names = true;

ASSERT_OK_AND_ASSIGN(SerdeJson json, MessageToJson(message, options));
ASSERT_OK_AND_ASSIGN(SimpleMessage parsed,
JsonToMessage<SimpleMessage>(json));
EXPECT_THAT(message, testing::EqualsProto(parsed));
}

TEST(MessageToJsonTest, SimpleMessageRoundTrip_WithOptions) {
proto2::json::PrintOptions print_options;
print_options.preserve_proto_field_names = true;

SimpleMessage message;
message.set_string_field("hello");
message.set_int32_field(123);
message.set_bool_field(true);
message.set_enum_field(SIMPLE_ENUM_C);

ASSERT_OK_AND_ASSIGN(SerdeJson json, MessageToJson(message, print_options));

ASSERT_OK_AND_ASSIGN(SimpleMessage parsed,
JsonToMessage<SimpleMessage>(json));
EXPECT_THAT(message, testing::EqualsProto(parsed));
}

TEST(MessageToJsonTest, ComplexMessageRoundTrip) {
ComplexMessage message;
message.mutable_simple()->set_string_field("nested_hello");
message.add_repeated_string("a");
message.add_repeated_string("b");
message.mutable_nested()->set_value("val");
message.add_repeated_nested()->set_value("val1");
message.add_repeated_nested()->set_value("val2");

ASSERT_OK_AND_ASSIGN(SerdeJson json, MessageToJson(message));

ASSERT_OK_AND_ASSIGN(ComplexMessage parsed,
JsonToMessage<ComplexMessage>(json));
ASSERT_THAT(message, testing::EqualsProto(parsed));
}

TEST(MessageToJsonTest, ComplexMessageRoundTrip_WithOptions) {
proto2::json::PrintOptions print_options;
print_options.preserve_proto_field_names = true;

ComplexMessage message;
message.mutable_simple()->set_string_field("nested_hello");
message.add_repeated_string("a");
message.add_repeated_string("b");
message.mutable_nested()->set_value("val");
message.add_repeated_nested()->set_value("val1");
message.add_repeated_nested()->set_value("val2");

ASSERT_OK_AND_ASSIGN(SerdeJson json, MessageToJson(message, print_options));

ASSERT_OK_AND_ASSIGN(ComplexMessage parsed,
JsonToMessage<ComplexMessage>(json));
ASSERT_THAT(message, testing::EqualsProto(parsed));
}

} // namespace
} // namespace security::json::serde_json_bridge::contrib
28 changes: 28 additions & 0 deletions serde_json/contrib/messages.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
syntax = "proto3";

package security.json.serde_json_bridge.contrib;

enum SimpleEnum {
SIMPLE_ENUM_UNSPECIFIED = 0;
SIMPLE_ENUM_A = 1;
SIMPLE_ENUM_B = 2;
SIMPLE_ENUM_C = 3;
}

message SimpleMessage {
string string_field = 1;
int32 int32_field = 2;
bool bool_field = 3;
SimpleEnum enum_field = 4;
}

message NestedMessage {
string value = 1;
}

message ComplexMessage {
SimpleMessage simple = 1;
repeated string repeated_string = 2;
NestedMessage nested = 3;
repeated NestedMessage repeated_nested = 4;
}
Loading