diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index 400c0cec40..a89312826b 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -456,3 +456,8 @@ Jared Stehler (@jaredstehler) Zhanghao (@zhangOranges) * Contributed #1305: Make helper methods of `WriterBasedJsonGenerator` non-final to allow overriding (2.18.0) + +Eduard Gomoliako (@Gems) + * Contributed #1356: Make `JsonGenerator::writeTypePrefix` method to not write a + `WRAPPER_ARRAY` when `typeIdDef.id == null` + (2.19.0) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index ad4b7179f1..d7062e8df2 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -17,6 +17,9 @@ a pure JSON library. 2.19.0 (not yet released) #1328: Optimize handling of `JsonPointer.head()` +#1356: Make `JsonGenerator::writeTypePrefix` method to not write a + `WRAPPER_ARRAY` when `typeIdDef.id == null` + (contributed by Eduard G) 2.18.1 (28-Oct-2024) diff --git a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java index 42ff2a9d82..cee202f465 100644 --- a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java @@ -10,6 +10,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.Objects; import com.fasterxml.jackson.core.JsonParser.NumberType; import com.fasterxml.jackson.core.exc.StreamWriteException; @@ -1968,61 +1969,94 @@ public void writeTypeId(Object id) throws IOException { */ public WritableTypeId writeTypePrefix(WritableTypeId typeIdDef) throws IOException { - Object id = typeIdDef.id; - - final JsonToken valueShape = typeIdDef.valueShape; - if (canWriteTypeId()) { - typeIdDef.wrapperWritten = false; - // just rely on native type output method (sub-classes likely to override) - writeTypeId(id); - } else { - // No native type id; write wrappers - // Normally we only support String type ids (non-String reserved for native type ids) - String idStr = (id instanceof String) ? (String) id : String.valueOf(id); - typeIdDef.wrapperWritten = true; - - Inclusion incl = typeIdDef.include; - // first: can not output "as property" if value not Object; if so, must do "as array" - if ((valueShape != JsonToken.START_OBJECT) - && incl.requiresObjectContext()) { - typeIdDef.include = incl = WritableTypeId.Inclusion.WRAPPER_ARRAY; + // Are native type ids allowed? If so, use them; otherwise, use wrappers + final boolean wasStartObjectWritten = canWriteTypeId() + ? _writeTypePrefixUsingNative(typeIdDef) + : _writeTypePrefixUsingWrapper(typeIdDef); + + // And then possible start marker for value itself: + switch (typeIdDef.valueShape) { + case START_OBJECT: + if (!wasStartObjectWritten) { + writeStartObject(typeIdDef.forValue); } + break; + case START_ARRAY: + writeStartArray(typeIdDef.forValue); + break; + default: // otherwise: no start marker + } - switch (incl) { - case PARENT_PROPERTY: - // nothing to do here, as it has to be written in suffix... - break; - case PAYLOAD_PROPERTY: - // only output as native type id; otherwise caller must handle using some - // other mechanism, so... - break; - case METADATA_PROPERTY: - // must have Object context by now, so simply write as field name - // Note, too, that it's bit tricky, since we must print START_OBJECT that is part - // of value first -- and then NOT output it later on: hence return "early" - writeStartObject(typeIdDef.forValue); - writeStringField(typeIdDef.asProperty, idStr); - return typeIdDef; + return typeIdDef; + } - case WRAPPER_OBJECT: - // NOTE: this is wrapper, not directly related to value to output, so don't pass - writeStartObject(); - writeFieldName(idStr); - break; - case WRAPPER_ARRAY: - default: // should never occur but translate as "as-array" - writeStartArray(); // wrapper, not actual array object to write - writeString(idStr); - } + /** + * Writes a native type id (when supported by format). + * + * @return True if start of an object has been written, False otherwise. + * + * @since 2.19 + */ + protected boolean _writeTypePrefixUsingNative(WritableTypeId typeIdDef) throws IOException { + typeIdDef.wrapperWritten = false; + writeTypeId(typeIdDef.id); + return false; + } + + /** + * Writes a wrapper for the type id if necessary. + * + * @return True if start of an object has been written, false otherwise. + * + * @since 2.19 + */ + protected boolean _writeTypePrefixUsingWrapper(WritableTypeId typeIdDef) throws IOException { + // Normally we only support String type ids (non-String reserved for native type ids) + final String id = Objects.toString(typeIdDef.id, null); + + // If we don't have Type ID we don't write a wrapper. + if (id == null) { + return false; } - // and finally possible start marker for value itself: - if (valueShape == JsonToken.START_OBJECT) { + + Inclusion incl = typeIdDef.include; + + // first: can not output "as property" if value not Object; if so, must do "as array" + if ((typeIdDef.valueShape != JsonToken.START_OBJECT) && incl.requiresObjectContext()) { + typeIdDef.include = incl = WritableTypeId.Inclusion.WRAPPER_ARRAY; + } + + typeIdDef.wrapperWritten = true; + + switch (incl) { + case PARENT_PROPERTY: + // nothing to do here, as it has to be written in suffix... + break; + case PAYLOAD_PROPERTY: + // only output as native type id; otherwise caller must handle using some + // other mechanism, so... + break; + case METADATA_PROPERTY: + // must have Object context by now, so simply write as field name + // Note, too, that it's bit tricky, since we must print START_OBJECT that is part + // of value first -- and then NOT output it later on: hence return "early" writeStartObject(typeIdDef.forValue); - } else if (valueShape == JsonToken.START_ARRAY) { - // should we now set the current object? - writeStartArray(); + writeStringField(typeIdDef.asProperty, id); + return true; + + case WRAPPER_OBJECT: + // NOTE: this is wrapper, not directly related to value to output, so + // do NOT pass "typeIdDef.forValue" + writeStartObject(); + writeFieldName(id); + break; + case WRAPPER_ARRAY: + default: // should never occur but translate as "as-array" + writeStartArray(); // wrapper, not actual array object to write + writeString(id); } - return typeIdDef; + + return false; } /**