diff --git a/velox/docs/functions/json.rst b/velox/docs/functions/json.rst index 8ebd16525699f..882927620ef1d 100644 --- a/velox/docs/functions/json.rst +++ b/velox/docs/functions/json.rst @@ -133,6 +133,13 @@ JSON Functions SELECT json_size('{"x": [1, 2, 3]}', '$.x'); -- 3 SELECT json_size('{"x": {"a": 1, "b": 2}}', '$.x.a'); -- 0 +.. function:: json_format(json) -> varchar + + Serializes the input JSON value to JSON text conforming to RFC 7159. + The JSON value can be a JSON object, a JSON array, a JSON string, a JSON number, true, false or null + + SELECT json_format(JSON '{"a": 1, "b": 2}') + ============ JSON Vectors ============ diff --git a/velox/functions/prestosql/CMakeLists.txt b/velox/functions/prestosql/CMakeLists.txt index f61c7f704909e..3df3a27062975 100644 --- a/velox/functions/prestosql/CMakeLists.txt +++ b/velox/functions/prestosql/CMakeLists.txt @@ -32,6 +32,7 @@ add_library( FromUnixTime.cpp GreatestLeast.cpp InPredicate.cpp + JsonFunctions.cpp Map.cpp MapEntries.cpp MapKeysAndValues.cpp diff --git a/velox/functions/prestosql/JsonFunctions.cpp b/velox/functions/prestosql/JsonFunctions.cpp new file mode 100644 index 0000000000000..8cf36c6b9d04b --- /dev/null +++ b/velox/functions/prestosql/JsonFunctions.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * 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 "velox/expression/VectorFunction.h" +#include "velox/functions/prestosql/Comparisons.h" + +namespace facebook::velox::functions { + +namespace { +class JsonFormatFunction : public exec::VectorFunction { + public: + void apply( + const SelectivityVector& rows, + std::vector& args, + const TypePtr& /* outputType */, + exec::EvalCtx& context, + VectorPtr& result) const override { + assert(args.size() == 1); + auto input = args[0]->asFlatVector(); + auto stringBuffers = input->stringBuffers(); + auto localResult = std::make_shared>( + context.pool(), + VARCHAR(), + input->nulls(), + rows.end(), + input->values(), + std::move(stringBuffers)); + + context.moveOrCopyResult(localResult, rows, result); + } + + static std::vector> signatures() { + // json -> varchar + return {exec::FunctionSignatureBuilder() + .returnType("Varchar") + .argumentType("Json") + .build()}; + } +}; + +} // namespace + +VELOX_DECLARE_VECTOR_FUNCTION( + udf_json_format, + JsonFormatFunction::signatures(), + std::make_unique()); +} // namespace facebook::velox::functions diff --git a/velox/functions/prestosql/registration/JsonFunctionsRegistration.cpp b/velox/functions/prestosql/registration/JsonFunctionsRegistration.cpp index 4f559a79717c5..015d834689ef3 100644 --- a/velox/functions/prestosql/registration/JsonFunctionsRegistration.cpp +++ b/velox/functions/prestosql/registration/JsonFunctionsRegistration.cpp @@ -35,6 +35,7 @@ void registerJsonFunctions() { registerFunction( {"json_array_contains"}); registerFunction({"json_size"}); + VELOX_REGISTER_VECTOR_FUNCTION(udf_json_format, "json_format"); } } // namespace facebook::velox::functions diff --git a/velox/functions/prestosql/tests/JsonFunctionsTest.cpp b/velox/functions/prestosql/tests/JsonFunctionsTest.cpp index 313ee1515b162..130848a531655 100644 --- a/velox/functions/prestosql/tests/JsonFunctionsTest.cpp +++ b/velox/functions/prestosql/tests/JsonFunctionsTest.cpp @@ -58,6 +58,37 @@ class JsonFunctionsTest : public functions::test::FunctionBaseTest { } }; +TEST_F(JsonFunctionsTest, JsonFormat) { + const auto jsonFormat = [&](std::optional value) { + return evaluateOnce( + "json_format(c0)", {value}, {JSON()}); + }; + + EXPECT_EQ(jsonFormat(R"(true)"), "true"); + EXPECT_EQ(jsonFormat(R"(null)"), "null"); + EXPECT_EQ(jsonFormat(R"(42)"), "42"); + EXPECT_EQ(jsonFormat(R"("abc")"), "\"abc\""); + EXPECT_EQ(jsonFormat(R"([1, 2, 3])"), "[1, 2, 3]"); + EXPECT_EQ(jsonFormat(R"({"k1":"v1"})"), "{\"k1\":\"v1\"}"); + + // check keys and values are there + const std::string jsonStr = jsonFormat(R"({"k1":"v1","k2":"v2"})").value(); + folly::dynamic object = folly::parseJson(jsonStr); + + std::set keys{"k1", "k2"}; + + for (const auto& key : object.keys()) { + EXPECT_TRUE(keys.find(key.getString()) != keys.end()); + } + + std::unordered_map jsonMap{ + {"k1", "v1"}, {"k2", "v2"}}; + + for (const auto& key : jsonMap) { + EXPECT_EQ(object.at(key.first), jsonMap.at(key.first)); + } +} + TEST_F(JsonFunctionsTest, isJsonScalarSignatures) { auto signatures = getSignatureStrings("is_json_scalar"); ASSERT_EQ(1, signatures.size());