Skip to content

Commit fafca5e

Browse files
committed
Add indentation parameter to json_format function
1 parent 36b1485 commit fafca5e

File tree

3 files changed

+109
-0
lines changed

3 files changed

+109
-0
lines changed

core/trino-main/src/main/java/io/trino/operator/scalar/JsonFunctions.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,18 @@
1414
package io.trino.operator.scalar;
1515

1616
import com.fasterxml.jackson.core.JsonFactory;
17+
import com.fasterxml.jackson.core.JsonGenerator;
1718
import com.fasterxml.jackson.core.JsonParser;
1819
import com.fasterxml.jackson.core.JsonToken;
20+
import com.fasterxml.jackson.core.util.DefaultIndenter;
21+
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
1922
import com.fasterxml.jackson.databind.MappingJsonFactory;
23+
import com.fasterxml.jackson.databind.ObjectMapper;
2024
import com.google.common.primitives.Doubles;
25+
import io.airlift.json.ObjectMapperProvider;
26+
import io.airlift.slice.DynamicSliceOutput;
2127
import io.airlift.slice.Slice;
28+
import io.airlift.slice.SliceOutput;
2229
import io.trino.plugin.base.util.JsonTypeUtil;
2330
import io.trino.spi.TrinoException;
2431
import io.trino.spi.function.LiteralParameter;
@@ -32,6 +39,7 @@
3239
import io.trino.type.JsonPathType;
3340

3441
import java.io.IOException;
42+
import java.io.OutputStream;
3543
import java.util.LinkedList;
3644
import java.util.List;
3745

@@ -45,6 +53,7 @@
4553
import static com.fasterxml.jackson.core.JsonToken.VALUE_NUMBER_INT;
4654
import static com.fasterxml.jackson.core.JsonToken.VALUE_STRING;
4755
import static com.fasterxml.jackson.core.JsonToken.VALUE_TRUE;
56+
import static com.fasterxml.jackson.databind.SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS;
4857
import static io.airlift.slice.Slices.utf8Slice;
4958
import static io.trino.plugin.base.util.JsonUtils.jsonFactoryBuilder;
5059
import static io.trino.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT;
@@ -61,6 +70,9 @@ public final class JsonFunctions
6170
private static final JsonFactory MAPPING_JSON_FACTORY = new MappingJsonFactory()
6271
.disable(CANONICALIZE_FIELD_NAMES);
6372

73+
private static final ObjectMapper SORTED_MAPPER = new ObjectMapperProvider().get()
74+
.configure(ORDER_MAP_ENTRIES_BY_KEYS, true);
75+
6476
private JsonFunctions() {}
6577

6678
@ScalarOperator(OperatorType.CAST)
@@ -124,6 +136,41 @@ public static Slice jsonFormat(@SqlType(StandardTypes.JSON) Slice slice)
124136
return slice;
125137
}
126138

139+
@ScalarFunction
140+
@SqlType(StandardTypes.VARCHAR)
141+
public static Slice jsonFormat(@SqlType(StandardTypes.JSON) Slice slice, @SqlType(StandardTypes.BIGINT) long indentSpaces)
142+
{
143+
if (indentSpaces < 0) {
144+
throw new TrinoException(INVALID_FUNCTION_ARGUMENT, "Indentation spaces must be non-negative, got: " + indentSpaces);
145+
}
146+
if (indentSpaces == 0) {
147+
return slice;
148+
}
149+
150+
try {
151+
Object jsonValue;
152+
try (JsonParser parser = createJsonParser(JSON_FACTORY, slice)) {
153+
jsonValue = SORTED_MAPPER.readValue(parser, Object.class);
154+
}
155+
156+
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
157+
String indentString = " ".repeat((int) indentSpaces);
158+
prettyPrinter.indentObjectsWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE.withIndent(indentString));
159+
prettyPrinter.indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE.withIndent(indentString));
160+
161+
SliceOutput output = new DynamicSliceOutput((int) (slice.length() * 1.5));
162+
try (JsonGenerator generator = JSON_FACTORY.createGenerator((OutputStream) output)) {
163+
generator.setPrettyPrinter(prettyPrinter);
164+
SORTED_MAPPER.writeValue(generator, jsonValue);
165+
}
166+
167+
return output.slice();
168+
}
169+
catch (IOException e) {
170+
throw new TrinoException(INVALID_FUNCTION_ARGUMENT, "Failed to format JSON: " + truncateIfNecessaryForErrorMessage(slice), e);
171+
}
172+
}
173+
127174
@ScalarFunction
128175
@LiteralParameters("x")
129176
@SqlType(StandardTypes.JSON)

core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonFunctions.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,42 @@ public void testJsonFormat()
699699
.isEqualTo("[\"a\",\"b\"]");
700700
}
701701

702+
@Test
703+
public void testJsonFormatWithIndentation()
704+
{
705+
assertThat(assertions.function("json_format", "JSON '{\"a\":1,\"b\":2}'", "2"))
706+
.hasType(VARCHAR)
707+
.isEqualTo("{\n \"a\" : 1,\n \"b\" : 2\n}");
708+
709+
assertThat(assertions.function("json_format", "JSON '{\"a\":1,\"b\":2}'", "4"))
710+
.hasType(VARCHAR)
711+
.isEqualTo("{\n \"a\" : 1,\n \"b\" : 2\n}");
712+
713+
assertThat(assertions.function("json_format", "JSON '{\"a\":1,\"b\":2}'", "0"))
714+
.hasType(VARCHAR)
715+
.isEqualTo("{\"a\":1,\"b\":2}");
716+
717+
assertThat(assertions.function("json_format", "JSON '{\"a\":{\"x\":1},\"b\":[1,2]}'", "2"))
718+
.hasType(VARCHAR)
719+
.isEqualTo("{\n \"a\" : {\n \"x\" : 1\n },\n \"b\" : [\n 1,\n 2\n ]\n}");
720+
721+
assertThat(assertions.function("json_format", "JSON '[1,2,3]'", "2"))
722+
.hasType(VARCHAR)
723+
.isEqualTo("[\n 1,\n 2,\n 3\n]");
724+
725+
assertThat(assertions.function("json_format", "JSON '{\"id\":9223372036854775807}'", "2"))
726+
.hasType(VARCHAR)
727+
.isEqualTo("{\n \"id\" : 9223372036854775807\n}");
728+
}
729+
730+
@Test
731+
public void testJsonFormatWithIndentationInvalid()
732+
{
733+
assertTrinoExceptionThrownBy(assertions.function("json_format", "JSON '{\"a\":1}'", "-1")::evaluate)
734+
.hasErrorCode(INVALID_FUNCTION_ARGUMENT)
735+
.hasMessageContaining("Indentation spaces must be non-negative");
736+
}
737+
702738
@Test
703739
public void testJsonSize()
704740
{

docs/src/main/sphinx/functions/json.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1894,6 +1894,32 @@ SELECT json_format(JSON '[1, 2, 3]'); -- '[1,2,3]'
18941894
SELECT json_format(JSON '"a"'); -- '"a"'
18951895
```
18961896

1897+
:::{function} json_format(json, indent_spaces) -> varchar
1898+
:no-index:
1899+
Returns the JSON text serialized from the input JSON value with specified
1900+
indentation. The `indent_spaces` parameter specifies the number of spaces to use
1901+
for indentation. This is useful for formatting JSON output in a human-readable
1902+
format, especially when working with clients that cannot handle large integers
1903+
in compact JSON strings.
1904+
1905+
```
1906+
SELECT json_format(JSON '{"a": 1, "b": 2}', 2);
1907+
-- '{
1908+
-- "a" : 1,
1909+
-- "b" : 2
1910+
-- }'
1911+
1912+
SELECT json_format(JSON '{"id": 9223372036854775807}', 2);
1913+
-- '{
1914+
-- "id" : 9223372036854775807
1915+
-- }'
1916+
```
1917+
1918+
When `indent_spaces` is 0, the function returns the compact format (same as
1919+
the single-argument version). The `indent_spaces` parameter must be
1920+
non-negative.
1921+
:::
1922+
18971923
:::{note}
18981924
{func}`json_format` and `CAST(json AS VARCHAR)` have completely
18991925
different semantics.

0 commit comments

Comments
 (0)