diff --git a/cpp/src/arrow/json/json_writer_internal.cc b/cpp/src/arrow/json/json_writer_internal.cc index 6d3e9ff9d85..e49841fda65 100644 --- a/cpp/src/arrow/json/json_writer_internal.cc +++ b/cpp/src/arrow/json/json_writer_internal.cc @@ -197,6 +197,12 @@ Result JsonWriter::GetString() const { return view; } +Result JsonWriter::GetPrettyString( + const simdjson::fractured_json_options& options) const { + ARROW_ASSIGN_OR_RAISE(std::string_view json, GetString()); + return simdjson::fractured_json_string(json, options); +} + void JsonWriter::Clear() { builder_.clear(); needs_comma_ = false; @@ -218,4 +224,9 @@ void JsonWriter::BoolField(std::string_view key, bool value) { Bool(value); } +void JsonWriter::IntField(std::string_view key, int32_t value) { + Key(key); + Int(value); +} + } // namespace arrow::json diff --git a/cpp/src/arrow/json/json_writer_internal.h b/cpp/src/arrow/json/json_writer_internal.h index 4a8200f1df3..e407695daa9 100644 --- a/cpp/src/arrow/json/json_writer_internal.h +++ b/cpp/src/arrow/json/json_writer_internal.h @@ -58,9 +58,13 @@ class ARROW_EXPORT JsonWriter { void StringField(std::string_view key, std::string_view value); void BoolField(std::string_view key, bool value); + void IntField(std::string_view key, int32_t value); Result GetString() const; + Result GetPrettyString( + const simdjson::fractured_json_options& options = {}) const; + void Clear(); private: diff --git a/cpp/src/arrow/json/json_writer_internal_test.cc b/cpp/src/arrow/json/json_writer_internal_test.cc index 7fc12e8adbe..a0123e80836 100644 --- a/cpp/src/arrow/json/json_writer_internal_test.cc +++ b/cpp/src/arrow/json/json_writer_internal_test.cc @@ -290,4 +290,47 @@ TEST(JsonWriter, WriteValueAllNumberTypes) { R"({"signed":-42,"unsigned":18446744073709551615,"double":2.5,"big":184467440737095516161234567890})"); } +TEST(JsonWriter, IntField) { + JsonWriter writer; + + writer.StartObject(); + writer.IntField("a", 42); + writer.EndObject(); + + ASSERT_OK_AND_ASSIGN(std::string_view json, writer.GetString()); + + EXPECT_EQ(json, R"({"a":42})"); +} + +TEST(JsonWriter, GetPrettyString) { + JsonWriter writer; + + writer.StartObject(); + writer.Key("a"); + writer.Int(42); + writer.Key("b"); + writer.String("hello"); + writer.EndObject(); + + ASSERT_OK_AND_ASSIGN(std::string pretty, writer.GetPrettyString()); + + // Pretty output should differ from the compact form (padded spacing, at minimum), + // even though a small object like this may still be rendered on one line. + EXPECT_NE(pretty, R"({"a":42,"b":"hello"})"); + + // But it should still parse back to the same values. + sj::parser parser; + simdjson::padded_string padded(pretty); + sj::document doc; + ASSERT_EQ(parser.iterate(padded).get(doc), simdjson::SUCCESS); + + int64_t a_value; + ASSERT_EQ(doc["a"].get(a_value), simdjson::SUCCESS); + EXPECT_EQ(a_value, 42); + + std::string_view b_value; + ASSERT_EQ(doc["b"].get(b_value), simdjson::SUCCESS); + EXPECT_EQ(b_value, "hello"); +} + } // namespace arrow::json diff --git a/cpp/src/parquet/printer.cc b/cpp/src/parquet/printer.cc index dfce57a00fc..dd6a21913bf 100644 --- a/cpp/src/parquet/printer.cc +++ b/cpp/src/parquet/printer.cc @@ -21,9 +21,11 @@ #include #include #include +#include #include #include +#include "arrow/json/json_writer_internal.h" #include "arrow/util/key_value_metadata.h" #include "arrow/util/string.h" @@ -254,16 +256,18 @@ void ParquetFilePrinter::DebugPrint(std::ostream& stream, std::list selecte void ParquetFilePrinter::JSONPrint(std::ostream& stream, std::list selected_columns, const char* filename) { const FileMetaData* file_metadata = fileReader->metadata().get(); - stream << "{\n"; - stream << " \"FileName\": \"" << filename << "\",\n"; - stream << " \"Version\": \"" << ParquetVersionToString(file_metadata->version()) - << "\",\n"; - stream << " \"CreatedBy\": \"" << file_metadata->created_by() << "\",\n"; - stream << " \"TotalRows\": \"" << file_metadata->num_rows() << "\",\n"; - stream << " \"NumberOfRowGroups\": \"" << file_metadata->num_row_groups() << "\",\n"; - stream << " \"NumberOfRealColumns\": \"" - << file_metadata->schema()->group_node()->field_count() << "\",\n"; - stream << " \"NumberOfColumns\": \"" << file_metadata->num_columns() << "\",\n"; + ::arrow::json::JsonWriter writer; + writer.StartObject(); + writer.StringField("FileName", filename); + writer.StringField("Version", ParquetVersionToString(file_metadata->version())); + writer.StringField("CreatedBy", file_metadata->created_by()); + writer.StringField("TotalRows", std::to_string(file_metadata->num_rows())); + writer.StringField("NumberOfRowGroups", + std::to_string(file_metadata->num_row_groups())); + writer.StringField( + "NumberOfRealColumns", + std::to_string(file_metadata->schema()->group_node()->field_count())); + writer.StringField("NumberOfColumns", std::to_string(file_metadata->num_columns())); if (selected_columns.empty()) { for (int i = 0; i < file_metadata->num_columns(); i++) { @@ -277,161 +281,152 @@ void ParquetFilePrinter::JSONPrint(std::ostream& stream, std::list selected } } - stream << " \"Columns\": [\n"; - int c = 0; + writer.Key("Columns"); + writer.StartArray(); for (auto i : selected_columns) { const ColumnDescriptor* descr = file_metadata->schema()->Column(i); - stream << " { \"Id\": \"" << i << "\"," - << " \"Name\": \"" << descr->path()->ToDotString() << "\"," - << " \"PhysicalType\": \"" - << TypeToString(descr->physical_type(), descr->type_length()) << "\"," - << " \"ConvertedType\": \"" << ConvertedTypeToString(descr->converted_type()) - << "\"," - << " \"LogicalType\": " << (descr->logical_type())->ToJSON() << " }"; - c++; - if (c != static_cast(selected_columns.size())) { - stream << ",\n"; - } + writer.StartObject(); + writer.StringField("Id", std::to_string(i)); + writer.StringField("Name", descr->path()->ToDotString()); + writer.StringField("PhysicalType", + TypeToString(descr->physical_type(), descr->type_length())); + writer.StringField("ConvertedType", ConvertedTypeToString(descr->converted_type())); + writer.Key("LogicalType"); + writer.RawValue(descr->logical_type()->ToJSON()); + writer.EndObject(); } + writer.EndArray(); - stream << "\n ],\n \"RowGroups\": [\n"; + writer.Key("RowGroups"); + writer.StartArray(); for (int r = 0; r < file_metadata->num_row_groups(); ++r) { - stream << " {\n \"Id\": \"" << r << "\", "; + writer.StartObject(); + writer.StringField("Id", std::to_string(r)); auto group_reader = fileReader->RowGroup(r); std::unique_ptr group_metadata = file_metadata->RowGroup(r); - stream << " \"TotalBytes\": \"" << group_metadata->total_byte_size() << "\", "; - stream << " \"TotalCompressedBytes\": \"" << group_metadata->total_compressed_size() - << "\", "; + writer.StringField("TotalBytes", std::to_string(group_metadata->total_byte_size())); + writer.StringField("TotalCompressedBytes", + std::to_string(group_metadata->total_compressed_size())); auto row_group_sorting_columns = group_metadata->sorting_columns(); if (!row_group_sorting_columns.empty()) { - stream << " \"SortColumns\": [\n"; - for (size_t i = 0; i < row_group_sorting_columns.size(); i++) { - stream << " {\"column_idx\": " << row_group_sorting_columns[i].column_idx - << ", \"descending\": " << row_group_sorting_columns[i].descending - << ", \"nulls_first\": " << row_group_sorting_columns[i].nulls_first - << "}"; - if (i + 1 != row_group_sorting_columns.size()) { - stream << ","; - } - stream << '\n'; + writer.Key("SortColumns"); + writer.StartArray(); + for (const auto& sorting_column : row_group_sorting_columns) { + writer.StartObject(); + writer.IntField("column_idx", sorting_column.column_idx); + writer.IntField("descending", sorting_column.descending); + writer.IntField("nulls_first", sorting_column.nulls_first); + writer.EndObject(); } - stream << " ], "; + writer.EndArray(); } - stream << " \"Rows\": \"" << group_metadata->num_rows() << "\",\n"; + writer.StringField("Rows", std::to_string(group_metadata->num_rows())); - // Print column metadata - stream << " \"ColumnChunks\": [\n"; - int c1 = 0; + writer.Key("ColumnChunks"); + writer.StartArray(); for (auto i : selected_columns) { auto column_chunk = group_metadata->ColumnChunk(i); std::shared_ptr stats = column_chunk->statistics(); - const ColumnDescriptor* descr = file_metadata->schema()->Column(i); - stream << " {\"Id\": \"" << i << "\", \"Values\": \"" - << column_chunk->num_values() << "\", " - << "\"StatsSet\": "; + + writer.StartObject(); + writer.StringField("Id", std::to_string(i)); + writer.StringField("Values", std::to_string(column_chunk->num_values())); if (column_chunk->is_stats_set()) { - stream << R"("True", "Stats": {)"; + writer.StringField("StatsSet", "True"); + writer.Key("Stats"); + writer.StartObject(); if (stats->HasNullCount()) { - stream << R"("NumNulls": ")" << stats->null_count() << "\""; + writer.StringField("NumNulls", std::to_string(stats->null_count())); } if (stats->HasDistinctCount()) { - stream << ", " - << R"("DistinctValues": ")" << stats->distinct_count() << "\""; + writer.StringField("DistinctValues", std::to_string(stats->distinct_count())); } if (stats->HasMinMax()) { std::string min = stats->EncodeMin(), max = stats->EncodeMax(); - stream << ", " - << R"("Max": ")" - << FormatStatValue(descr->physical_type(), max, descr->logical_type()) - << "\", " - << R"("Min": ")" - << FormatStatValue(descr->physical_type(), min, descr->logical_type()) - << "\""; + writer.StringField( + "Max", FormatStatValue(descr->physical_type(), max, descr->logical_type())); + writer.StringField( + "Min", FormatStatValue(descr->physical_type(), min, descr->logical_type())); if (stats->is_max_value_exact().has_value()) { - stream << ", " - << R"("IsMaxValueExact": ")" - << (stats->is_max_value_exact().value() ? "True" : "False") << "\""; + writer.StringField("IsMaxValueExact", + stats->is_max_value_exact().value() ? "True" : "False"); } else { - stream << ", " - << R"("IsMaxValueExact": "unknown")"; + writer.StringField("IsMaxValueExact", "unknown"); } if (stats->is_min_value_exact().has_value()) { - stream << ", " - << R"("IsMinValueExact": ")" - << (stats->is_min_value_exact().value() ? "True" : "False") << "\""; + writer.StringField("IsMinValueExact", + stats->is_min_value_exact().value() ? "True" : "False"); } else { - stream << ", " - << R"("IsMinValueExact": "unknown")"; + writer.StringField("IsMinValueExact", "unknown"); } } - stream << " },"; + writer.EndObject(); } else { - stream << "\"False\","; + writer.StringField("StatsSet", "False"); } - stream << "\n \"Compression\": \"" - << ::arrow::internal::AsciiToUpper( - Codec::GetCodecAsString(column_chunk->compression())) - << R"(", "Encodings": )"; - stream << "\""; + + writer.StringField("Compression", + ::arrow::internal::AsciiToUpper( + Codec::GetCodecAsString(column_chunk->compression()))); + + std::ostringstream encodings_stream; if (column_chunk->encoding_stats().empty()) { for (auto encoding : column_chunk->encodings()) { - stream << EncodingToString(encoding) << " "; + encodings_stream << EncodingToString(encoding) << " "; } } else { - PrintPageEncodingStats(stream, column_chunk->encoding_stats()); + PrintPageEncodingStats(encodings_stream, column_chunk->encoding_stats()); } - stream << "\""; - stream << ", " - << R"("UncompressedSize": ")" << column_chunk->total_uncompressed_size() - << R"(", "CompressedSize": ")" << column_chunk->total_compressed_size() - << "\""; + writer.StringField("Encodings", encodings_stream.str()); + + writer.StringField("UncompressedSize", + std::to_string(column_chunk->total_uncompressed_size())); + writer.StringField("CompressedSize", + std::to_string(column_chunk->total_compressed_size())); if (column_chunk->bloom_filter_offset()) { - // Output BloomFilter {offset, length} - stream << ", \"BloomFilter\": {" - << R"("offset": ")" << column_chunk->bloom_filter_offset().value() << "\""; + writer.Key("BloomFilter"); + writer.StartObject(); + writer.StringField("offset", + std::to_string(column_chunk->bloom_filter_offset().value())); if (column_chunk->bloom_filter_length()) { - stream << R"(, "length": ")" << column_chunk->bloom_filter_length().value() - << "\""; + writer.StringField("length", + std::to_string(column_chunk->bloom_filter_length().value())); } - stream << "}"; + writer.EndObject(); } if (column_chunk->GetColumnIndexLocation()) { auto location = column_chunk->GetColumnIndexLocation().value(); - // Output ColumnIndex {offset, length} - stream << ", \"ColumnIndex\": {" - << R"("offset": ")" << location.offset; - stream << R"(", "length": ")" << location.length; - stream << "\"}"; + writer.Key("ColumnIndex"); + writer.StartObject(); + writer.StringField("offset", std::to_string(location.offset)); + writer.StringField("length", std::to_string(location.length)); + writer.EndObject(); } if (column_chunk->GetOffsetIndexLocation()) { auto location = column_chunk->GetOffsetIndexLocation().value(); - // Output OffsetIndex {offset, length} - stream << ", \"OffsetIndex\": {" - << R"("offset": ")" << location.offset << "\""; - stream << R"(, "length": ")" << location.length << "\""; - stream << "}"; + writer.Key("OffsetIndex"); + writer.StartObject(); + writer.StringField("offset", std::to_string(location.offset)); + writer.StringField("length", std::to_string(location.length)); + writer.EndObject(); } - // end of a ColumnChunk - stream << " }"; - c1++; - if (c1 != static_cast(selected_columns.size())) { - stream << ",\n"; - } - } - - stream << "\n ]\n }"; - if ((r + 1) != static_cast(file_metadata->num_row_groups())) { - stream << ",\n"; + writer.EndObject(); } + writer.EndArray(); + writer.EndObject(); } - stream << "\n ]\n}\n"; + writer.EndArray(); + writer.EndObject(); + + PARQUET_ASSIGN_OR_THROW(std::string pretty_json, writer.GetPrettyString()); + stream << pretty_json << "\n"; } } // namespace parquet diff --git a/cpp/src/parquet/reader_test.cc b/cpp/src/parquet/reader_test.cc index cdeee116fbc..eeb839e71fe 100644 --- a/cpp/src/parquet/reader_test.cc +++ b/cpp/src/parquet/reader_test.cc @@ -1103,28 +1103,64 @@ class TestJSONWithLocalFile : public ::testing::Test { TEST_F(TestJSONWithLocalFile, JSONOutputWithStatistics) { std::string json_output = R"###({ - "FileName": "nested_lists.snappy.parquet", - "Version": "1.0", - "CreatedBy": "parquet-mr version 1.8.2 (build c6522788629e590a53eb79874b95f6c3ff11f16c)", - "TotalRows": "3", - "NumberOfRowGroups": "1", - "NumberOfRealColumns": "2", - "NumberOfColumns": "2", - "Columns": [ - { "Id": "0", "Name": "a.list.element.list.element.list.element", "PhysicalType": "BYTE_ARRAY", "ConvertedType": "UTF8", "LogicalType": {"Type": "String"} }, - { "Id": "1", "Name": "b", "PhysicalType": "INT32", "ConvertedType": "NONE", "LogicalType": {"Type": "None"} } - ], - "RowGroups": [ - { - "Id": "0", "TotalBytes": "155", "TotalCompressedBytes": "0", "Rows": "3", - "ColumnChunks": [ - {"Id": "0", "Values": "18", "StatsSet": "False", - "Compression": "SNAPPY", "Encodings": "PLAIN_DICTIONARY(DICT_PAGE) PLAIN_DICTIONARY", "UncompressedSize": "103", "CompressedSize": "104" }, - {"Id": "1", "Values": "3", "StatsSet": "True", "Stats": {"NumNulls": "0", "Max": "1", "Min": "1", "IsMaxValueExact": "unknown", "IsMinValueExact": "unknown" }, - "Compression": "SNAPPY", "Encodings": "PLAIN_DICTIONARY(DICT_PAGE) PLAIN_DICTIONARY", "UncompressedSize": "52", "CompressedSize": "56" } - ] - } - ] + "FileName": "nested_lists.snappy.parquet", + "Version": "1.0", + "CreatedBy": "parquet-mr version 1.8.2 (build c6522788629e590a53eb79874b95f6c3ff11f16c)", + "TotalRows": "3", + "NumberOfRowGroups": "1", + "NumberOfRealColumns": "2", + "NumberOfColumns": "2", + "Columns": [ + { + "Id": "0", + "Name": "a.list.element.list.element.list.element", + "PhysicalType": "BYTE_ARRAY", + "ConvertedType": "UTF8", + "LogicalType": { "Type": "String" } + }, + { + "Id": "1", + "Name": "b", + "PhysicalType": "INT32", + "ConvertedType": "NONE", + "LogicalType": { "Type": "None" } + } + ], + "RowGroups": [ + { + "Id": "0", + "TotalBytes": "155", + "TotalCompressedBytes": "0", + "Rows": "3", + "ColumnChunks": [ + { + "Id": "0", + "Values": "18", + "StatsSet": "False", + "Compression": "SNAPPY", + "Encodings": "PLAIN_DICTIONARY(DICT_PAGE) PLAIN_DICTIONARY", + "UncompressedSize": "103", + "CompressedSize": "104" + }, + { + "Id": "1", + "Values": "3", + "StatsSet": "True", + "Stats": { + "NumNulls": "0", + "Max": "1", + "Min": "1", + "IsMaxValueExact": "unknown", + "IsMinValueExact": "unknown" + }, + "Compression": "SNAPPY", + "Encodings": "PLAIN_DICTIONARY(DICT_PAGE) PLAIN_DICTIONARY", + "UncompressedSize": "52", + "CompressedSize": "56" + } + ] + } + ] } )###"; @@ -1134,55 +1170,47 @@ TEST_F(TestJSONWithLocalFile, JSONOutputWithStatistics) { TEST_F(TestJSONWithLocalFile, JSONOutput) { std::string json_output = R"###({ - "FileName": "alltypes_plain.parquet", - "Version": "1.0", - "CreatedBy": "impala version 1.3.0-INTERNAL (build 8a48ddb1eff84592b3fc06bc6f51ec120e1fffc9)", - "TotalRows": "8", - "NumberOfRowGroups": "1", - "NumberOfRealColumns": "11", - "NumberOfColumns": "11", - "Columns": [ - { "Id": "0", "Name": "id", "PhysicalType": "INT32", "ConvertedType": "NONE", "LogicalType": {"Type": "None"} }, - { "Id": "1", "Name": "bool_col", "PhysicalType": "BOOLEAN", "ConvertedType": "NONE", "LogicalType": {"Type": "None"} }, - { "Id": "2", "Name": "tinyint_col", "PhysicalType": "INT32", "ConvertedType": "NONE", "LogicalType": {"Type": "None"} }, - { "Id": "3", "Name": "smallint_col", "PhysicalType": "INT32", "ConvertedType": "NONE", "LogicalType": {"Type": "None"} }, - { "Id": "4", "Name": "int_col", "PhysicalType": "INT32", "ConvertedType": "NONE", "LogicalType": {"Type": "None"} }, - { "Id": "5", "Name": "bigint_col", "PhysicalType": "INT64", "ConvertedType": "NONE", "LogicalType": {"Type": "None"} }, - { "Id": "6", "Name": "float_col", "PhysicalType": "FLOAT", "ConvertedType": "NONE", "LogicalType": {"Type": "None"} }, - { "Id": "7", "Name": "double_col", "PhysicalType": "DOUBLE", "ConvertedType": "NONE", "LogicalType": {"Type": "None"} }, - { "Id": "8", "Name": "date_string_col", "PhysicalType": "BYTE_ARRAY", "ConvertedType": "NONE", "LogicalType": {"Type": "None"} }, - { "Id": "9", "Name": "string_col", "PhysicalType": "BYTE_ARRAY", "ConvertedType": "NONE", "LogicalType": {"Type": "None"} }, - { "Id": "10", "Name": "timestamp_col", "PhysicalType": "INT96", "ConvertedType": "NONE", "LogicalType": {"Type": "None"} } - ], - "RowGroups": [ - { - "Id": "0", "TotalBytes": "671", "TotalCompressedBytes": "0", "Rows": "8", - "ColumnChunks": [ - {"Id": "0", "Values": "8", "StatsSet": "False", - "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "UncompressedSize": "73", "CompressedSize": "73" }, - {"Id": "1", "Values": "8", "StatsSet": "False", - "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "UncompressedSize": "24", "CompressedSize": "24" }, - {"Id": "2", "Values": "8", "StatsSet": "False", - "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "UncompressedSize": "47", "CompressedSize": "47" }, - {"Id": "3", "Values": "8", "StatsSet": "False", - "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "UncompressedSize": "47", "CompressedSize": "47" }, - {"Id": "4", "Values": "8", "StatsSet": "False", - "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "UncompressedSize": "47", "CompressedSize": "47" }, - {"Id": "5", "Values": "8", "StatsSet": "False", - "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "UncompressedSize": "55", "CompressedSize": "55" }, - {"Id": "6", "Values": "8", "StatsSet": "False", - "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "UncompressedSize": "47", "CompressedSize": "47" }, - {"Id": "7", "Values": "8", "StatsSet": "False", - "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "UncompressedSize": "55", "CompressedSize": "55" }, - {"Id": "8", "Values": "8", "StatsSet": "False", - "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "UncompressedSize": "88", "CompressedSize": "88" }, - {"Id": "9", "Values": "8", "StatsSet": "False", - "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "UncompressedSize": "49", "CompressedSize": "49" }, - {"Id": "10", "Values": "8", "StatsSet": "False", - "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "UncompressedSize": "139", "CompressedSize": "139" } - ] - } - ] + "FileName": "alltypes_plain.parquet", + "Version": "1.0", + "CreatedBy": "impala version 1.3.0-INTERNAL (build 8a48ddb1eff84592b3fc06bc6f51ec120e1fffc9)", + "TotalRows": "8", + "NumberOfRowGroups": "1", + "NumberOfRealColumns": "11", + "NumberOfColumns": "11", + "Columns": [ + { "ConvertedType": "NONE", "Id": "0" , "LogicalType": { "Type": "None" }, "Name": "id" , "PhysicalType": "INT32" }, + { "ConvertedType": "NONE", "Id": "1" , "LogicalType": { "Type": "None" }, "Name": "bool_col" , "PhysicalType": "BOOLEAN" }, + { "ConvertedType": "NONE", "Id": "2" , "LogicalType": { "Type": "None" }, "Name": "tinyint_col" , "PhysicalType": "INT32" }, + { "ConvertedType": "NONE", "Id": "3" , "LogicalType": { "Type": "None" }, "Name": "smallint_col" , "PhysicalType": "INT32" }, + { "ConvertedType": "NONE", "Id": "4" , "LogicalType": { "Type": "None" }, "Name": "int_col" , "PhysicalType": "INT32" }, + { "ConvertedType": "NONE", "Id": "5" , "LogicalType": { "Type": "None" }, "Name": "bigint_col" , "PhysicalType": "INT64" }, + { "ConvertedType": "NONE", "Id": "6" , "LogicalType": { "Type": "None" }, "Name": "float_col" , "PhysicalType": "FLOAT" }, + { "ConvertedType": "NONE", "Id": "7" , "LogicalType": { "Type": "None" }, "Name": "double_col" , "PhysicalType": "DOUBLE" }, + { "ConvertedType": "NONE", "Id": "8" , "LogicalType": { "Type": "None" }, "Name": "date_string_col", "PhysicalType": "BYTE_ARRAY" }, + { "ConvertedType": "NONE", "Id": "9" , "LogicalType": { "Type": "None" }, "Name": "string_col" , "PhysicalType": "BYTE_ARRAY" }, + { "ConvertedType": "NONE", "Id": "10", "LogicalType": { "Type": "None" }, "Name": "timestamp_col" , "PhysicalType": "INT96" } + ], + "RowGroups": [ + { + "Id": "0", + "TotalBytes": "671", + "TotalCompressedBytes": "0", + "Rows": "8", + "ColumnChunks": [ + { "CompressedSize": "73" , "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "Id": "0" , "StatsSet": "False", "UncompressedSize": "73" , "Values": "8" }, + { "CompressedSize": "24" , "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "Id": "1" , "StatsSet": "False", "UncompressedSize": "24" , "Values": "8" }, + { "CompressedSize": "47" , "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "Id": "2" , "StatsSet": "False", "UncompressedSize": "47" , "Values": "8" }, + { "CompressedSize": "47" , "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "Id": "3" , "StatsSet": "False", "UncompressedSize": "47" , "Values": "8" }, + { "CompressedSize": "47" , "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "Id": "4" , "StatsSet": "False", "UncompressedSize": "47" , "Values": "8" }, + { "CompressedSize": "55" , "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "Id": "5" , "StatsSet": "False", "UncompressedSize": "55" , "Values": "8" }, + { "CompressedSize": "47" , "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "Id": "6" , "StatsSet": "False", "UncompressedSize": "47" , "Values": "8" }, + { "CompressedSize": "55" , "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "Id": "7" , "StatsSet": "False", "UncompressedSize": "55" , "Values": "8" }, + { "CompressedSize": "88" , "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "Id": "8" , "StatsSet": "False", "UncompressedSize": "88" , "Values": "8" }, + { "CompressedSize": "49" , "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "Id": "9" , "StatsSet": "False", "UncompressedSize": "49" , "Values": "8" }, + { "CompressedSize": "139", "Compression": "UNCOMPRESSED", "Encodings": "RLE PLAIN_DICTIONARY PLAIN ", "Id": "10", "StatsSet": "False", "UncompressedSize": "139", "Values": "8" } + ] + } + ] } )###"; @@ -1196,16 +1224,22 @@ TEST_F(TestJSONWithLocalFile, JSONOutputFLBA) { std::string json_content = ReadFromLocalFile("fixed_length_byte_array.parquet"); std::string json_contains = R"###({ - "FileName": "fixed_length_byte_array.parquet", - "Version": "1.0", - "CreatedBy": "parquet-mr version 1.13.0-SNAPSHOT (build d057b39d93014fe40f5067ee4a33621e65c91552)", - "TotalRows": "1000", - "NumberOfRowGroups": "1", - "NumberOfRealColumns": "1", - "NumberOfColumns": "1", - "Columns": [ - { "Id": "0", "Name": "flba_field", "PhysicalType": "FIXED_LEN_BYTE_ARRAY(4)", "ConvertedType": "NONE", "LogicalType": {"Type": "None"} } - ])###"; + "FileName": "fixed_length_byte_array.parquet", + "Version": "1.0", + "CreatedBy": "parquet-mr version 1.13.0-SNAPSHOT (build d057b39d93014fe40f5067ee4a33621e65c91552)", + "TotalRows": "1000", + "NumberOfRowGroups": "1", + "NumberOfRealColumns": "1", + "NumberOfColumns": "1", + "Columns": [ + { + "Id": "0", + "Name": "flba_field", + "PhysicalType": "FIXED_LEN_BYTE_ARRAY(4)", + "ConvertedType": "NONE", + "LogicalType": { "Type": "None" } + } + ],)###"; EXPECT_THAT(json_content, testing::HasSubstr(json_contains)); } @@ -1214,9 +1248,8 @@ TEST_F(TestJSONWithLocalFile, JSONOutputSortColumns) { std::string json_content = ReadFromLocalFile("sort_columns.parquet"); std::string json_contains = R"###("SortColumns": [ - {"column_idx": 0, "descending": 1, "nulls_first": 1}, - {"column_idx": 1, "descending": 0, "nulls_first": 0} - ])###"; + { "column_idx": 0, "descending": 1, "nulls_first": 1 }, { "column_idx": 1, "descending": 0, "nulls_first": 0 } + ],)###"; EXPECT_THAT(json_content, testing::HasSubstr(json_contains)); } diff --git a/cpp/src/parquet/schema_test.cc b/cpp/src/parquet/schema_test.cc index 704b2da79c1..3888e4f8d95 100644 --- a/cpp/src/parquet/schema_test.cc +++ b/cpp/src/parquet/schema_test.cc @@ -1495,120 +1495,120 @@ TEST(TestLogicalTypeOperation, LogicalTypeRepresentation) { }; std::vector cases = { - {UndefinedLogicalType::Make(), "Undefined", R"({"Type": "Undefined"})"}, - {LogicalType::String(), "String", R"({"Type": "String"})"}, - {LogicalType::Map(), "Map", R"({"Type": "Map"})"}, - {LogicalType::List(), "List", R"({"Type": "List"})"}, - {LogicalType::Enum(), "Enum", R"({"Type": "Enum"})"}, + {UndefinedLogicalType::Make(), "Undefined", R"({"Type":"Undefined"})"}, + {LogicalType::String(), "String", R"({"Type":"String"})"}, + {LogicalType::Map(), "Map", R"({"Type":"Map"})"}, + {LogicalType::List(), "List", R"({"Type":"List"})"}, + {LogicalType::Enum(), "Enum", R"({"Type":"Enum"})"}, {LogicalType::Decimal(10, 4), "Decimal(precision=10, scale=4)", - R"({"Type": "Decimal", "precision": 10, "scale": 4})"}, + R"({"Type":"Decimal","precision":10,"scale":4})"}, {LogicalType::Decimal(10), "Decimal(precision=10, scale=0)", - R"({"Type": "Decimal", "precision": 10, "scale": 0})"}, - {LogicalType::Date(), "Date", R"({"Type": "Date"})"}, + R"({"Type":"Decimal","precision":10,"scale":0})"}, + {LogicalType::Date(), "Date", R"({"Type":"Date"})"}, {LogicalType::Time(true, LogicalType::TimeUnit::MILLIS), "Time(isAdjustedToUTC=true, timeUnit=milliseconds)", - R"({"Type": "Time", "isAdjustedToUTC": true, "timeUnit": "milliseconds"})"}, + R"({"Type":"Time","isAdjustedToUTC":true,"timeUnit":"milliseconds"})"}, {LogicalType::Time(true, LogicalType::TimeUnit::MICROS), "Time(isAdjustedToUTC=true, timeUnit=microseconds)", - R"({"Type": "Time", "isAdjustedToUTC": true, "timeUnit": "microseconds"})"}, + R"({"Type":"Time","isAdjustedToUTC":true,"timeUnit":"microseconds"})"}, {LogicalType::Time(true, LogicalType::TimeUnit::NANOS), "Time(isAdjustedToUTC=true, timeUnit=nanoseconds)", - R"({"Type": "Time", "isAdjustedToUTC": true, "timeUnit": "nanoseconds"})"}, + R"({"Type":"Time","isAdjustedToUTC":true,"timeUnit":"nanoseconds"})"}, {LogicalType::Time(false, LogicalType::TimeUnit::MILLIS), "Time(isAdjustedToUTC=false, timeUnit=milliseconds)", - R"({"Type": "Time", "isAdjustedToUTC": false, "timeUnit": "milliseconds"})"}, + R"({"Type":"Time","isAdjustedToUTC":false,"timeUnit":"milliseconds"})"}, {LogicalType::Time(false, LogicalType::TimeUnit::MICROS), "Time(isAdjustedToUTC=false, timeUnit=microseconds)", - R"({"Type": "Time", "isAdjustedToUTC": false, "timeUnit": "microseconds"})"}, + R"({"Type":"Time","isAdjustedToUTC":false,"timeUnit":"microseconds"})"}, {LogicalType::Time(false, LogicalType::TimeUnit::NANOS), "Time(isAdjustedToUTC=false, timeUnit=nanoseconds)", - R"({"Type": "Time", "isAdjustedToUTC": false, "timeUnit": "nanoseconds"})"}, + R"({"Type":"Time","isAdjustedToUTC":false,"timeUnit":"nanoseconds"})"}, {LogicalType::Timestamp(true, LogicalType::TimeUnit::MILLIS), "Timestamp(isAdjustedToUTC=true, timeUnit=milliseconds, " "is_from_converted_type=false, force_set_converted_type=false)", - R"({"Type": "Timestamp", "isAdjustedToUTC": true, "timeUnit": "milliseconds", )" - R"("is_from_converted_type": false, "force_set_converted_type": false})"}, + R"({"Type":"Timestamp","isAdjustedToUTC":true,"timeUnit":"milliseconds",)" + R"("is_from_converted_type":false,"force_set_converted_type":false})"}, {LogicalType::Timestamp(true, LogicalType::TimeUnit::MICROS), "Timestamp(isAdjustedToUTC=true, timeUnit=microseconds, " "is_from_converted_type=false, force_set_converted_type=false)", - R"({"Type": "Timestamp", "isAdjustedToUTC": true, "timeUnit": "microseconds", )" - R"("is_from_converted_type": false, "force_set_converted_type": false})"}, + R"({"Type":"Timestamp","isAdjustedToUTC":true,"timeUnit":"microseconds",)" + R"("is_from_converted_type":false,"force_set_converted_type":false})"}, {LogicalType::Timestamp(true, LogicalType::TimeUnit::NANOS), "Timestamp(isAdjustedToUTC=true, timeUnit=nanoseconds, " "is_from_converted_type=false, force_set_converted_type=false)", - R"({"Type": "Timestamp", "isAdjustedToUTC": true, "timeUnit": "nanoseconds", )" - R"("is_from_converted_type": false, "force_set_converted_type": false})"}, + R"({"Type":"Timestamp","isAdjustedToUTC":true,"timeUnit":"nanoseconds",)" + R"("is_from_converted_type":false,"force_set_converted_type":false})"}, {LogicalType::Timestamp(false, LogicalType::TimeUnit::MILLIS, true, true), "Timestamp(isAdjustedToUTC=false, timeUnit=milliseconds, " "is_from_converted_type=true, force_set_converted_type=true)", - R"({"Type": "Timestamp", "isAdjustedToUTC": false, "timeUnit": "milliseconds", )" - R"("is_from_converted_type": true, "force_set_converted_type": true})"}, + R"({"Type":"Timestamp","isAdjustedToUTC":false,"timeUnit":"milliseconds",)" + R"("is_from_converted_type":true,"force_set_converted_type":true})"}, {LogicalType::Timestamp(false, LogicalType::TimeUnit::MICROS), "Timestamp(isAdjustedToUTC=false, timeUnit=microseconds, " "is_from_converted_type=false, force_set_converted_type=false)", - R"({"Type": "Timestamp", "isAdjustedToUTC": false, "timeUnit": "microseconds", )" - R"("is_from_converted_type": false, "force_set_converted_type": false})"}, + R"({"Type":"Timestamp","isAdjustedToUTC":false,"timeUnit":"microseconds",)" + R"("is_from_converted_type":false,"force_set_converted_type":false})"}, {LogicalType::Timestamp(false, LogicalType::TimeUnit::NANOS), "Timestamp(isAdjustedToUTC=false, timeUnit=nanoseconds, " "is_from_converted_type=false, force_set_converted_type=false)", - R"({"Type": "Timestamp", "isAdjustedToUTC": false, "timeUnit": "nanoseconds", )" - R"("is_from_converted_type": false, "force_set_converted_type": false})"}, - {LogicalType::Interval(), "Interval", R"({"Type": "Interval"})"}, + R"({"Type":"Timestamp","isAdjustedToUTC":false,"timeUnit":"nanoseconds",)" + R"("is_from_converted_type":false,"force_set_converted_type":false})"}, + {LogicalType::Interval(), "Interval", R"({"Type":"Interval"})"}, {LogicalType::Int(8, false), "Int(bitWidth=8, isSigned=false)", - R"({"Type": "Int", "bitWidth": 8, "isSigned": false})"}, + R"({"Type":"Int","bitWidth":8,"isSigned":false})"}, {LogicalType::Int(16, false), "Int(bitWidth=16, isSigned=false)", - R"({"Type": "Int", "bitWidth": 16, "isSigned": false})"}, + R"({"Type":"Int","bitWidth":16,"isSigned":false})"}, {LogicalType::Int(32, false), "Int(bitWidth=32, isSigned=false)", - R"({"Type": "Int", "bitWidth": 32, "isSigned": false})"}, + R"({"Type":"Int","bitWidth":32,"isSigned":false})"}, {LogicalType::Int(64, false), "Int(bitWidth=64, isSigned=false)", - R"({"Type": "Int", "bitWidth": 64, "isSigned": false})"}, + R"({"Type":"Int","bitWidth":64,"isSigned":false})"}, {LogicalType::Int(8, true), "Int(bitWidth=8, isSigned=true)", - R"({"Type": "Int", "bitWidth": 8, "isSigned": true})"}, + R"({"Type":"Int","bitWidth":8,"isSigned":true})"}, {LogicalType::Int(16, true), "Int(bitWidth=16, isSigned=true)", - R"({"Type": "Int", "bitWidth": 16, "isSigned": true})"}, + R"({"Type":"Int","bitWidth":16,"isSigned":true})"}, {LogicalType::Int(32, true), "Int(bitWidth=32, isSigned=true)", - R"({"Type": "Int", "bitWidth": 32, "isSigned": true})"}, + R"({"Type":"Int","bitWidth":32,"isSigned":true})"}, {LogicalType::Int(64, true), "Int(bitWidth=64, isSigned=true)", - R"({"Type": "Int", "bitWidth": 64, "isSigned": true})"}, - {LogicalType::Null(), "Null", R"({"Type": "Null"})"}, - {LogicalType::JSON(), "JSON", R"({"Type": "JSON"})"}, - {LogicalType::BSON(), "BSON", R"({"Type": "BSON"})"}, - {LogicalType::UUID(), "UUID", R"({"Type": "UUID"})"}, - {LogicalType::Float16(), "Float16", R"({"Type": "Float16"})"}, - {LogicalType::Geometry(), "Geometry(crs=)", R"({"Type": "Geometry"})"}, + R"({"Type":"Int","bitWidth":64,"isSigned":true})"}, + {LogicalType::Null(), "Null", R"({"Type":"Null"})"}, + {LogicalType::JSON(), "JSON", R"({"Type":"JSON"})"}, + {LogicalType::BSON(), "BSON", R"({"Type":"BSON"})"}, + {LogicalType::UUID(), "UUID", R"({"Type":"UUID"})"}, + {LogicalType::Float16(), "Float16", R"({"Type":"Float16"})"}, + {LogicalType::Geometry(), "Geometry(crs=)", R"({"Type":"Geometry"})"}, {LogicalType::Geometry("srid:1234"), "Geometry(crs=srid:1234)", - R"({"Type": "Geometry", "crs": "srid:1234"})"}, + R"({"Type":"Geometry","crs":"srid:1234"})"}, {LogicalType::Geometry(R"(crs with "quotes" and \backslashes\)"), R"(Geometry(crs=crs with "quotes" and \backslashes\))", - R"({"Type": "Geometry", "crs": "crs with \"quotes\" and \\backslashes\\"})"}, + R"({"Type":"Geometry","crs":"crs with \"quotes\" and \\backslashes\\"})"}, {LogicalType::Geometry("crs with control characters \u0001 and \u001f"), "Geometry(crs=crs with control characters \u0001 and \u001f)", - R"({"Type": "Geometry", "crs": "crs with control characters \u0001 and \u001f"})"}, + R"({"Type":"Geometry","crs":"crs with control characters \u0001 and \u001f"})"}, {LogicalType::Geography(), "Geography(crs=, algorithm=spherical)", - R"({"Type": "Geography"})"}, + R"({"Type":"Geography"})"}, {LogicalType::Geography("srid:1234", LogicalType::EdgeInterpolationAlgorithm::SPHERICAL), "Geography(crs=srid:1234, algorithm=spherical)", - R"({"Type": "Geography", "crs": "srid:1234"})"}, + R"({"Type":"Geography","crs":"srid:1234"})"}, {LogicalType::Geography("srid:1234", LogicalType::EdgeInterpolationAlgorithm::VINCENTY), "Geography(crs=srid:1234, algorithm=vincenty)", - R"({"Type": "Geography", "crs": "srid:1234", "algorithm": "vincenty"})"}, + R"({"Type":"Geography","crs":"srid:1234","algorithm":"vincenty"})"}, {LogicalType::Geography("srid:1234", LogicalType::EdgeInterpolationAlgorithm::THOMAS), "Geography(crs=srid:1234, algorithm=thomas)", - R"({"Type": "Geography", "crs": "srid:1234", "algorithm": "thomas"})"}, + R"({"Type":"Geography","crs":"srid:1234","algorithm":"thomas"})"}, {LogicalType::Geography("srid:1234", LogicalType::EdgeInterpolationAlgorithm::ANDOYER), "Geography(crs=srid:1234, algorithm=andoyer)", - R"({"Type": "Geography", "crs": "srid:1234", "algorithm": "andoyer"})"}, + R"({"Type":"Geography","crs":"srid:1234","algorithm":"andoyer"})"}, {LogicalType::Geography("srid:1234", LogicalType::EdgeInterpolationAlgorithm::KARNEY), "Geography(crs=srid:1234, algorithm=karney)", - R"({"Type": "Geography", "crs": "srid:1234", "algorithm": "karney"})"}, - {LogicalType::Variant(), "Variant(1)", R"({"Type": "Variant", "SpecVersion": 1})"}, - {LogicalType::Variant(2), "Variant(2)", R"({"Type": "Variant", "SpecVersion": 2})"}, - {LogicalType::None(), "None", R"({"Type": "None"})"}, + R"({"Type":"Geography","crs":"srid:1234","algorithm":"karney"})"}, + {LogicalType::Variant(), "Variant(1)", R"({"Type":"Variant","SpecVersion":1})"}, + {LogicalType::Variant(2), "Variant(2)", R"({"Type":"Variant","SpecVersion":2})"}, + {LogicalType::None(), "None", R"({"Type":"None"})"}, }; for (const ExpectedRepresentation& c : cases) { diff --git a/cpp/src/parquet/types.cc b/cpp/src/parquet/types.cc index cc3199f367a..534c79201c0 100644 --- a/cpp/src/parquet/types.cc +++ b/cpp/src/parquet/types.cc @@ -717,9 +717,12 @@ class LogicalType::Impl { } virtual std::string ToJSON() const { - std::stringstream json; - json << R"({"Type": ")" << ToString() << R"("})"; - return json.str(); + ::arrow::json::JsonWriter writer; + writer.StartObject(); + writer.StringField("Type", ToString()); + writer.EndObject(); + PARQUET_ASSIGN_OR_THROW(std::string_view json, writer.GetString()); + return std::string(json); } virtual format::LogicalType ToThrift() const { @@ -1171,10 +1174,14 @@ std::string LogicalType::Impl::Decimal::ToString() const { } std::string LogicalType::Impl::Decimal::ToJSON() const { - std::stringstream json; - json << R"({"Type": "Decimal", "precision": )" << precision_ << R"(, "scale": )" - << scale_ << "}"; - return json.str(); + ::arrow::json::JsonWriter writer; + writer.StartObject(); + writer.StringField("Type", "Decimal"); + writer.IntField("precision", precision_); + writer.IntField("scale", scale_); + writer.EndObject(); + PARQUET_ASSIGN_OR_THROW(std::string_view json, writer.GetString()); + return std::string(json); } format::LogicalType LogicalType::Impl::Decimal::ToThrift() const { @@ -1316,10 +1323,14 @@ std::string LogicalType::Impl::Time::ToString() const { } std::string LogicalType::Impl::Time::ToJSON() const { - std::stringstream json; - json << R"({"Type": "Time", "isAdjustedToUTC": )" << std::boolalpha << adjusted_ - << R"(, "timeUnit": ")" << time_unit_string(unit_) << R"("})"; - return json.str(); + ::arrow::json::JsonWriter writer; + writer.StartObject(); + writer.StringField("Type", "Time"); + writer.BoolField("isAdjustedToUTC", adjusted_); + writer.StringField("timeUnit", time_unit_string(unit_)); + writer.EndObject(); + PARQUET_ASSIGN_OR_THROW(std::string_view json, writer.GetString()); + return std::string(json); } format::LogicalType LogicalType::Impl::Time::ToThrift() const { @@ -1463,12 +1474,16 @@ std::string LogicalType::Impl::Timestamp::ToString() const { } std::string LogicalType::Impl::Timestamp::ToJSON() const { - std::stringstream json; - json << R"({"Type": "Timestamp", "isAdjustedToUTC": )" << std::boolalpha << adjusted_ - << R"(, "timeUnit": ")" << time_unit_string(unit_) << R"(")" - << R"(, "is_from_converted_type": )" << is_from_converted_type_ - << R"(, "force_set_converted_type": )" << force_set_converted_type_ << R"(})"; - return json.str(); + ::arrow::json::JsonWriter writer; + writer.StartObject(); + writer.StringField("Type", "Timestamp"); + writer.BoolField("isAdjustedToUTC", adjusted_); + writer.StringField("timeUnit", time_unit_string(unit_)); + writer.BoolField("is_from_converted_type", is_from_converted_type_); + writer.BoolField("force_set_converted_type", force_set_converted_type_); + writer.EndObject(); + PARQUET_ASSIGN_OR_THROW(std::string_view json, writer.GetString()); + return std::string(json); } format::LogicalType LogicalType::Impl::Timestamp::ToThrift() const { @@ -1653,10 +1668,14 @@ std::string LogicalType::Impl::Int::ToString() const { } std::string LogicalType::Impl::Int::ToJSON() const { - std::stringstream json; - json << R"({"Type": "Int", "bitWidth": )" << width_ << R"(, "isSigned": )" - << std::boolalpha << signed_ << "}"; - return json.str(); + ::arrow::json::JsonWriter writer; + writer.StartObject(); + writer.StringField("Type", "Int"); + writer.IntField("bitWidth", width_); + writer.BoolField("isSigned", signed_); + writer.EndObject(); + PARQUET_ASSIGN_OR_THROW(std::string_view json, writer.GetString()); + return std::string(json); } format::LogicalType LogicalType::Impl::Int::ToThrift() const { @@ -1778,16 +1797,6 @@ class LogicalType::Impl::Float16 final : public LogicalType::Impl::Incompatible, GENERATE_MAKE(Float16) -namespace { -void WriteCrsKeyAndValue(const std::string_view crs, std::ostream& json) { - // There is no restriction on the crs value here, and it may contain quotes - // or backslashes that would result in invalid JSON if unescaped. - ::arrow::json::JsonWriter writer; - writer.String(crs); - json << R"(, "crs": )" << writer.GetString().ValueUnsafe(); -} -} // namespace - class LogicalType::Impl::Geometry final : public LogicalType::Impl::Incompatible, public LogicalType::Impl::SimpleApplicable { public: @@ -1816,15 +1825,15 @@ std::string LogicalType::Impl::Geometry::ToString() const { } std::string LogicalType::Impl::Geometry::ToJSON() const { - std::stringstream json; - json << R"({"Type": "Geometry")"; - + ::arrow::json::JsonWriter writer; + writer.StartObject(); + writer.StringField("Type", "Geometry"); if (!crs_.empty()) { - WriteCrsKeyAndValue(crs_, json); + writer.StringField("crs", crs_); } - - json << "}"; - return json.str(); + writer.EndObject(); + PARQUET_ASSIGN_OR_THROW(std::string_view json, writer.GetString()); + return std::string(json); } format::LogicalType LogicalType::Impl::Geometry::ToThrift() const { @@ -1907,19 +1916,18 @@ std::string LogicalType::Impl::Geography::ToString() const { } std::string LogicalType::Impl::Geography::ToJSON() const { - std::stringstream json; - json << R"({"Type": "Geography")"; - + ::arrow::json::JsonWriter writer; + writer.StartObject(); + writer.StringField("Type", "Geography"); if (!crs_.empty()) { - WriteCrsKeyAndValue(crs_, json); + writer.StringField("crs", crs_); } - if (algorithm_ != LogicalType::EdgeInterpolationAlgorithm::SPHERICAL) { - json << R"(, "algorithm": ")" << algorithm_name() << R"(")"; + writer.StringField("algorithm", algorithm_name()); } - - json << "}"; - return json.str(); + writer.EndObject(); + PARQUET_ASSIGN_OR_THROW(std::string_view json, writer.GetString()); + return std::string(json); } format::LogicalType LogicalType::Impl::Geography::ToThrift() const { @@ -2000,11 +2008,13 @@ std::string LogicalType::Impl::Variant::ToString() const { } std::string LogicalType::Impl::Variant::ToJSON() const { - std::stringstream json; - json << R"({"Type": "Variant", "SpecVersion": )" << static_cast(spec_version_) - << "}"; - - return json.str(); + ::arrow::json::JsonWriter writer; + writer.StartObject(); + writer.StringField("Type", "Variant"); + writer.IntField("SpecVersion", static_cast(spec_version_)); + writer.EndObject(); + PARQUET_ASSIGN_OR_THROW(std::string_view json, writer.GetString()); + return std::string(json); } format::LogicalType LogicalType::Impl::Variant::ToThrift() const {