From 3336b7834fb63d866355f40ab1aa73bfd0829aba Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Thu, 17 Aug 2023 10:38:15 +0200 Subject: [PATCH] [jOOQ/jOOQ#6028] Add JSONFormat.NullFormat to offer different NULL encoding options --- jOOQ/src/main/java/org/jooq/JSONFormat.java | 117 ++++++++++++++++++ .../java/org/jooq/impl/AbstractResult.java | 47 ++++--- 2 files changed, 150 insertions(+), 14 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/JSONFormat.java b/jOOQ/src/main/java/org/jooq/JSONFormat.java index 6a4ee2fb67..92684d9a88 100644 --- a/jOOQ/src/main/java/org/jooq/JSONFormat.java +++ b/jOOQ/src/main/java/org/jooq/JSONFormat.java @@ -81,6 +81,8 @@ public final class JSONFormat { String[] indented; boolean header; RecordFormat recordFormat; + NullFormat objectNulls; + NullFormat arrayNulls; boolean wrapSingleColumnRecords; boolean quoteNested; @@ -94,6 +96,8 @@ public final class JSONFormat { null, true, RecordFormat.ARRAY, + NullFormat.NULL_ON_NULL, + NullFormat.NULL_ON_NULL, true, false ); @@ -108,6 +112,8 @@ public final class JSONFormat { String[] indented, boolean header, RecordFormat recordFormat, + NullFormat objectNulls, + NullFormat arrayNulls, boolean wrapSingleColumnRecords, boolean quoteNested ) { @@ -124,6 +130,8 @@ public final class JSONFormat { }; this.header = header; this.recordFormat = recordFormat; + this.objectNulls = objectNulls; + this.arrayNulls = arrayNulls; this.wrapSingleColumnRecords = wrapSingleColumnRecords; this.quoteNested = quoteNested; } @@ -150,6 +158,8 @@ public final class JSONFormat { null, header, recordFormat, + objectNulls, + arrayNulls, wrapSingleColumnRecords, quoteNested ); @@ -176,6 +186,8 @@ public final class JSONFormat { null, header, recordFormat, + objectNulls, + arrayNulls, wrapSingleColumnRecords, quoteNested ); @@ -207,6 +219,8 @@ public final class JSONFormat { indented, header, recordFormat, + objectNulls, + arrayNulls, wrapSingleColumnRecords, quoteNested ); @@ -239,6 +253,8 @@ public final class JSONFormat { null, header, recordFormat, + objectNulls, + arrayNulls, wrapSingleColumnRecords, quoteNested ); @@ -270,6 +286,8 @@ public final class JSONFormat { null, header, recordFormat, + objectNulls, + arrayNulls, wrapSingleColumnRecords, quoteNested ); @@ -317,6 +335,8 @@ public final class JSONFormat { indented, newHeader, recordFormat, + objectNulls, + arrayNulls, wrapSingleColumnRecords, quoteNested ); @@ -350,6 +370,8 @@ public final class JSONFormat { indented, header, newRecordFormat, + objectNulls, + arrayNulls, wrapSingleColumnRecords, quoteNested ); @@ -364,6 +386,78 @@ public final class JSONFormat { return recordFormat; } + /** + * The null format to be applied to objects, defaulting to + * {@link NullFormat#NULL_ON_NULL}. + */ + @NotNull + public final JSONFormat objectNulls(NullFormat newObjectNulls) { + if (mutable) { + objectNulls = newObjectNulls; + return this; + } + else + return new JSONFormat( + mutable, + format, + newline, + globalIndent, + indent, + indented, + header, + recordFormat, + newObjectNulls, + arrayNulls, + wrapSingleColumnRecords, + quoteNested + ); + } + + /** + * The null format to be applied to objects, defaulting to + * {@link NullFormat#NULL_ON_NULL}. + */ + @NotNull + public final NullFormat objectNulls() { + return objectNulls; + } + + /** + * The null format to be applied to arrays, defaulting to + * {@link NullFormat#NULL_ON_NULL}. + */ + @NotNull + public final JSONFormat arrayNulls(NullFormat newArrayNulls) { + if (mutable) { + arrayNulls = newArrayNulls; + return this; + } + else + return new JSONFormat( + mutable, + format, + newline, + globalIndent, + indent, + indented, + header, + recordFormat, + objectNulls, + newArrayNulls, + wrapSingleColumnRecords, + quoteNested + ); + } + + /** + * The null format to be applied to arrays, defaulting to + * {@link NullFormat#NULL_ON_NULL}. + */ + @NotNull + public final NullFormat arrayNulls() { + return arrayNulls; + } + /** * Whether to wrap single column records in the {@link #recordFormat()}. */ @@ -383,6 +477,8 @@ public final class JSONFormat { indented, header, recordFormat, + objectNulls, + arrayNulls, newWrapSingleColumnRecords, quoteNested ); @@ -415,6 +511,8 @@ public final class JSONFormat { indented, header, recordFormat, + objectNulls, + arrayNulls, wrapSingleColumnRecords, newQuoteNested ); @@ -451,4 +549,23 @@ public final class JSONFormat { */ OBJECT, } + + /** + * The format of null values in JSON objects or arrays. + */ + public enum NullFormat { + + /** + * A null value in source data is represented by an + * explicit null value in the JSON object (the key is + * present) or array. + */ + NULL_ON_NULL, + + /** + * A null value in source data is represented by an absent + * value in the JSON object (the key is absent) or array. + */ + ABSENT_ON_NULL, + } } diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractResult.java b/jOOQ/src/main/java/org/jooq/impl/AbstractResult.java index 5b90dba48a..24ea99ddc4 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractResult.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractResult.java @@ -40,6 +40,7 @@ package org.jooq.impl; import static java.lang.Math.max; import static java.lang.Math.min; import static java.util.stream.Collectors.joining; +import static org.jooq.JSONFormat.NullFormat.ABSENT_ON_NULL; import static org.jooq.XMLFormat.RecordFormat.COLUMN_NAME_ELEMENTS; import static org.jooq.XMLFormat.RecordFormat.VALUE_ELEMENTS_WITH_FIELD_ATTRIBUTE; import static org.jooq.conf.SettingsTools.renderLocale; @@ -71,6 +72,7 @@ import javax.xml.parsers.ParserConfigurationException; import org.jooq.CSVFormat; import org.jooq.ChartFormat; import org.jooq.ChartFormat.Display; +import org.jooq.JSONFormat.NullFormat; import org.jooq.Configuration; import org.jooq.Constants; import org.jooq.Cursor; @@ -560,6 +562,9 @@ abstract class AbstractResult extends AbstractFormattable impl switch (format.recordFormat()) { case ARRAY: for (Record record : this) { + if (record == null && format.arrayNulls() == ABSENT_ON_NULL) + continue; + hasRecords = true; writer.append(separator); @@ -573,6 +578,9 @@ abstract class AbstractResult extends AbstractFormattable impl break; case OBJECT: for (Record record : this) { + if (record == null && format.objectNulls() == ABSENT_ON_NULL) + continue; + hasRecords = true; writer.append(separator); @@ -628,31 +636,32 @@ abstract class AbstractResult extends AbstractFormattable impl else if (value instanceof Object[] array) { writer.append('['); - for (int i = 0; i < array.length; i++) { - if (i > 0) + boolean first = true; + for (Object o : array) { + if (o == null && format.arrayNulls() == ABSENT_ON_NULL) + continue; + + if (!first) writer.append(','); - formatJSON0(array[i], writer, format); + formatJSON0(o, writer, format); + first = false; } writer.append(']'); } // [#7782] Nested records should generate nested JSON data structures - else if (value instanceof Formattable f) { + else if (value instanceof Formattable f) f.formatJSON(writer, format); - } - else if (value instanceof JSON && !format.quoteNested()) { + // [#10744] TODO: Possibly parse and format JSON and JSONB content as well + else if (value instanceof JSON && !format.quoteNested()) writer.write(((JSON) value).data()); - } - else if (value instanceof JSONB && !format.quoteNested()) { + else if (value instanceof JSONB && !format.quoteNested()) writer.write(((JSONB) value).data()); - } - - else { + else JSONValue.writeJSONString(value, writer); - } } static final void formatJSONMap0( @@ -675,6 +684,11 @@ abstract class AbstractResult extends AbstractFormattable impl writer.append('{'); for (int index = 0; index < size; index++) { + Object value = record.get(index); + + if (value == null && format.objectNulls() == ABSENT_ON_NULL) + continue; + writer.append(separator); if (format.format()) @@ -692,7 +706,7 @@ abstract class AbstractResult extends AbstractFormattable impl } int previous = format.globalIndent(); - formatJSON0(record.get(index), writer, format.globalIndent(format.globalIndent() + format.indent() * (recordLevel + 1))); + formatJSON0(value, writer, format.globalIndent(format.globalIndent() + format.indent() * (recordLevel + 1))); format.globalIndent(previous); if (format.format() && format.wrapSingleColumnRecords() && size == 1) @@ -728,6 +742,11 @@ abstract class AbstractResult extends AbstractFormattable impl writer.append('['); for (int index = 0; index < size; index++) { + Object value = record.get(index); + + if (value == null && format.arrayNulls() == ABSENT_ON_NULL) + continue; + writer.append(separator); if (format.format()) @@ -737,7 +756,7 @@ abstract class AbstractResult extends AbstractFormattable impl writer.append(' '); int previous = format.globalIndent(); - formatJSON0(record.get(index), writer, format.globalIndent(format.globalIndent() + format.indent() * (recordLevel + 1))); + formatJSON0(value, writer, format.globalIndent(format.globalIndent() + format.indent() * (recordLevel + 1))); format.globalIndent(previous); if (format.format() && format.wrapSingleColumnRecords() && size == 1)