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)