This is an automated email from the ASF dual-hosted git repository.

fokko pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/avro.git


The following commit(s) were added to refs/heads/main by this push:
     new 1305509ac AVRO-4026: [C++] Allow non-string custom attributes (#3344)
1305509ac is described below

commit 1305509ace25931b3373d35daf47bc48238455b6
Author: Gang Wu <[email protected]>
AuthorDate: Fri Mar 21 17:04:38 2025 +0800

    AVRO-4026: [C++] Allow non-string custom attributes (#3344)
---
 lang/c++/impl/Compiler.cc                 |  3 +-
 lang/c++/impl/CustomAttributes.cc         | 15 +++++++--
 lang/c++/include/avro/CustomAttributes.hh |  7 +++-
 lang/c++/test/SchemaTests.cc              | 56 +++++++++++++++++++++++++++++++
 4 files changed, 76 insertions(+), 5 deletions(-)

diff --git a/lang/c++/impl/Compiler.cc b/lang/c++/impl/Compiler.cc
index 6879c81e5..5f59fb5ba 100644
--- a/lang/c++/impl/Compiler.cc
+++ b/lang/c++/impl/Compiler.cc
@@ -291,7 +291,8 @@ static void getCustomAttributes(const Object &m, 
CustomAttributes &customAttribu
     const std::unordered_set<std::string> &kKnownFields = getKnownFields();
     for (const auto &entry : m) {
         if (kKnownFields.find(entry.first) == kKnownFields.end()) {
-            customAttributes.addAttribute(entry.first, 
entry.second.toLiteralString());
+            bool addQuotes = entry.second.type() == json::EntityType::String;
+            customAttributes.addAttribute(entry.first, 
entry.second.toLiteralString(), addQuotes);
         }
     }
 }
diff --git a/lang/c++/impl/CustomAttributes.cc 
b/lang/c++/impl/CustomAttributes.cc
index f31b557ae..4c139ba5a 100644
--- a/lang/c++/impl/CustomAttributes.cc
+++ b/lang/c++/impl/CustomAttributes.cc
@@ -35,19 +35,28 @@ std::optional<std::string> 
CustomAttributes::getAttribute(const std::string &nam
 }
 
 void CustomAttributes::addAttribute(const std::string &name,
-                                    const std::string &value) {
+                                    const std::string &value,
+                                    bool addQuotes) {
     auto iter_and_find =
         attributes_.insert(std::pair<std::string, std::string>(name, value));
     if (!iter_and_find.second) {
         throw Exception(name + " already exists and cannot be added");
     }
+    if (addQuotes) {
+        keysNeedQuotes_.insert(name);
+    }
 }
 
 void CustomAttributes::printJson(std::ostream &os,
                                  const std::string &name) const {
-    if (attributes().find(name) == attributes().end()) {
+    auto iter = attributes_.find(name);
+    if (iter == attributes_.cend()) {
         throw Exception(name + " doesn't exist");
     }
-    os << "\"" << name << "\": \"" << attributes().at(name) << "\"";
+    if (keysNeedQuotes_.find(name) != keysNeedQuotes_.cend()) {
+        os << "\"" << name << "\": \"" << iter->second << "\"";
+    } else {
+        os << "\"" << name << "\": " << iter->second;
+    }
 }
 } // namespace avro
diff --git a/lang/c++/include/avro/CustomAttributes.hh 
b/lang/c++/include/avro/CustomAttributes.hh
index 4b76b45a5..72f4acaec 100644
--- a/lang/c++/include/avro/CustomAttributes.hh
+++ b/lang/c++/include/avro/CustomAttributes.hh
@@ -24,6 +24,7 @@
 #include <map>
 #include <optional>
 #include <string>
+#include <unordered_set>
 
 namespace avro {
 
@@ -37,7 +38,10 @@ public:
     std::optional<std::string> getAttribute(const std::string &name) const;
 
     // Adds a custom attribute. If the attribute already exists, throw an 
exception.
-    void addAttribute(const std::string &name, const std::string &value);
+    //
+    // If `addQuotes` is true, the `value` will be wrapped in double quotes in 
the
+    // json serialization; otherwise, the `value` will be serialized as is.
+    void addAttribute(const std::string &name, const std::string &value, bool 
addQuotes = true);
 
     // Provides a way to iterate over the custom attributes or check attribute 
size.
     const std::map<std::string, std::string> &attributes() const {
@@ -49,6 +53,7 @@ public:
 
 private:
     std::map<std::string, std::string> attributes_;
+    std::unordered_set<std::string> keysNeedQuotes_;
 };
 
 } // namespace avro
diff --git a/lang/c++/test/SchemaTests.cc b/lang/c++/test/SchemaTests.cc
index 4830ef04c..39e2224af 100644
--- a/lang/c++/test/SchemaTests.cc
+++ b/lang/c++/test/SchemaTests.cc
@@ -856,6 +856,60 @@ static void testAddCustomAttributes() {
     BOOST_CHECK_EQUAL(removeWhitespaceFromSchema(json), 
removeWhitespaceFromSchema(expected));
 }
 
+static void testCustomAttributesJson2Schema2Json() {
+    const std::string schema = R"({
+        "type": "record",
+        "name": "my_record",
+        "fields": [
+            { "name": "long_field", "type": "long", "int_key": 1, "str_key": 
"1" }
+        ]
+    })";
+    ValidSchema compiledSchema = compileJsonSchemaFromString(schema);
+
+    // Verify custom attributes from parsed schema
+    auto customAttributes = compiledSchema.root()->customAttributesAt(0);
+    BOOST_CHECK_EQUAL(customAttributes.getAttribute("int_key").value(), "1");
+    BOOST_CHECK_EQUAL(customAttributes.getAttribute("str_key").value(), "1");
+
+    // Verify custom attributes from json result
+    std::string json = compiledSchema.toJson();
+    BOOST_CHECK_EQUAL(removeWhitespaceFromSchema(json), 
removeWhitespaceFromSchema(schema));
+}
+
+static void testCustomAttributesSchema2Json2Schema() {
+    const std::string expected = R"({
+        "type": "record",
+        "name": "my_record",
+        "fields": [
+            { "name": "long_field", "type": "long", "int_key": 1, "str_key": 
"1" }
+        ]
+    })";
+
+    auto recordNode = std::make_shared<NodeRecord>();
+    {
+        CustomAttributes customAttributes;
+        customAttributes.addAttribute("int_key", "1", /*addQuotes=*/false);
+        customAttributes.addAttribute("str_key", "1", /*addQuotes=*/true);
+        recordNode->addCustomAttributesForField(customAttributes);
+        recordNode->addLeaf(std::make_shared<NodePrimitive>(AVRO_LONG));
+        recordNode->addName("long_field");
+        recordNode->setName(Name("my_record"));
+    }
+
+    // Verify custom attributes from json result
+    ValidSchema schema(recordNode);
+    std::string json = schema.toJson();
+    BOOST_CHECK_EQUAL(removeWhitespaceFromSchema(json), 
removeWhitespaceFromSchema(expected));
+
+    // Verify custom attributes from parsed schema
+    {
+        auto parsedSchema = compileJsonSchemaFromString(json);
+        auto customAttributes = parsedSchema.root()->customAttributesAt(0);
+        BOOST_CHECK_EQUAL(customAttributes.getAttribute("int_key").value(), 
"1");
+        BOOST_CHECK_EQUAL(customAttributes.getAttribute("str_key").value(), 
"1");
+    }
+}
+
 } // namespace schema
 } // namespace avro
 
@@ -882,5 +936,7 @@ init_unit_test_suite(int /*argc*/, char * /*argv*/[]) {
     ts->add(BOOST_TEST_CASE(&avro::schema::testCustomLogicalType));
     ts->add(BOOST_TEST_CASE(&avro::schema::testParseCustomAttributes));
     ts->add(BOOST_TEST_CASE(&avro::schema::testAddCustomAttributes));
+    
ts->add(BOOST_TEST_CASE(&avro::schema::testCustomAttributesJson2Schema2Json));
+    
ts->add(BOOST_TEST_CASE(&avro::schema::testCustomAttributesSchema2Json2Schema));
     return ts;
 }

Reply via email to