Fix #242
diff --git a/release-notes/VERSION b/release-notes/VERSION
index 9ee5ce8..31fc96c 100644
--- a/release-notes/VERSION
+++ b/release-notes/VERSION
@@ -21,6 +21,7 @@
#57: Add support for non-blocking ("async") JSON parsing
#208: Make use of `_matchCount` in `FilteringParserDelegate`
(contributed by Rafal F)
+#242: Add new write methods in `JsonGenerator` for writing type id containers
#304: Optimize `NumberOutput.outputLong()` method
#306: Add new method in `JsonStreamContext` to construct `JsonPointer`
#312: Add `JsonProcessingException.clearLocation()` to allow clearing
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java
index dbda832..5816cb7 100644
--- a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java
+++ b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java
@@ -13,6 +13,8 @@
import com.fasterxml.jackson.core.JsonParser.NumberType;
import com.fasterxml.jackson.core.io.CharacterEscapes;
+import com.fasterxml.jackson.core.type.WritableTypeId;
+import com.fasterxml.jackson.core.type.WritableTypeId.Inclusion;
import com.fasterxml.jackson.core.util.VersionUtil;
import static com.fasterxml.jackson.core.JsonTokenId.*;
@@ -1406,11 +1408,6 @@
throw new JsonGenerationException("No native support for writing Type Ids", this);
}
- // 24-May-2016, tatu: Looks like this won't quite make it in 2.8... too
- // many open questions on whether return value may be used and such to
- // really close the loop. But leaving code sample in, in case we can resolve it
- // it for 2.9.
-
/*
* Replacement method for {@link #writeTypeId(Object)} which is called
* regardless of whether format has native type ids. If it does have native
@@ -1418,60 +1415,105 @@
* structural type id inclusion is to be used. For JSON, for example, no
* native type ids exist and structural inclusion is always used.
*<p>
- * NOTE: from databind perspective, only "as-wrapper-array", "as-wrapper-object" and
- * "as-property" inclusion styles call this method; the remaining "as-external-property"
- * mechanism always uses writes type id value as simple property.
- *
- * @param inclStyle Kind of inclusion; {@link JsonToken#START_ARRAY} for "as-wrapper-array",
- * {@link JsonToken#START_OBJECT} for "as-wrapper-object" and {@link JsonToken#FIELD_NAME}
- * for "as-property"
- * @param forValue Java object for which type is being written; not used by standard mechanism
- * @param valueShape Expected shape of the value to write, as expressed by the first token (for
- * structural type), or any of scalar types for non-structured values (typically
- * just {@link JsonToken#VALUE_STRING} -- exact token not required, just the fact it's scalar)
- * @param typeId Type id to write
- * @param propertyName Name of property to use, in case of "as-property" inclusion style
+ * NOTE: databind may choose to skip calling this method for some special cases
+ * (and instead included type id via regular write methods and/or {@link #writeTypeId}
+ * -- this is discouraged, but not illegal, and may be necessary as a work-around
+ * in some cases.
*
* @since 2.8
*/
- /*
- public Object writeTypeSuffix(JsonToken inclStyle, Object forValue, JsonToken valueShape,
- String typeId, String propertyName) throws IOException
+ public WritableTypeId writeTypePrefix(WritableTypeId typeIdDef) throws IOException
{
- if (inclStyle == JsonToken.FIELD_NAME) { // as-property
- if (typeId == null) { // should not include `null` type id in any form with this style
- writeStartObject();
- } else if (valueShape == JsonToken.START_OBJECT) {
- if (canWriteTypeId()) {
- writeTypeId(typeId);
- writeStartObject();
- } else {
- writeStartObject();
- writeStringField(propertyName, typeId);
- }
- } else if (valueShape == JsonToken.START_ARRAY) {
- if (canWriteTypeId()) {
- writeTypeId(typeId);
- writeStartArray();
- } else {
- writeStartArray();
- writeString(typeId);
- }
- } else { // any scalar
- if (canWriteTypeId()) {
- writeTypeId(typeId);
- }
- }
- return JsonToken.END_OBJECT;
- }
- if (inclStyle == JsonToken.START_ARRAY) { // as-wrapper-array
- } else if (inclStyle == JsonToken.START_OBJECT) { // as-wrapper-object
-
+ 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 {
- throw new JsonGenerationException("Unrecognized inclusion style: "+inclStyle, this);
+ // 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;
+ }
+
+ 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();
+ writeStringField(typeIdDef.asProperty, idStr);
+ return typeIdDef;
+
+ case WRAPPER_OBJECT:
+ writeStartObject();
+ writeFieldName(idStr);
+ break;
+ case WRAPPER_ARRAY:
+ default: // should never occur but translate as "as-array"
+ writeStartArray();
+ writeString(idStr);
+ }
}
+ // and finally possible start marker for value itself:
+ if (valueShape == JsonToken.START_OBJECT) {
+ writeStartObject();
+ } else if (valueShape == JsonToken.START_ARRAY) {
+ writeStartArray();
+ }
+ return typeIdDef;
}
- */
+
+ public WritableTypeId writeTypeSuffix(WritableTypeId typeIdDef) throws IOException
+ {
+ final JsonToken valueShape = typeIdDef.valueShape;
+ // First: does value need closing?
+ if (valueShape == JsonToken.START_OBJECT) {
+ writeEndObject();
+ } else if (valueShape == JsonToken.START_ARRAY) {
+ writeEndArray();
+ }
+
+ if (typeIdDef.wrapperWritten) {
+ switch (typeIdDef.include) {
+ case WRAPPER_ARRAY:
+ writeEndArray();
+ break;
+ case PARENT_PROPERTY:
+ // unusually, need to output AFTER value. And no real wrapper...
+ {
+ Object id = typeIdDef.id;
+ String idStr = (id instanceof String) ? (String) id : String.valueOf(id);
+ writeStringField(typeIdDef.asProperty, idStr);
+ }
+ break;
+ case METADATA_PROPERTY:
+ case PAYLOAD_PROPERTY:
+ // no actual wrapper; included within Object itself
+ break;
+ case WRAPPER_OBJECT:
+ default: // should never occur but...
+ writeEndObject();
+ break;
+ }
+ }
+ return typeIdDef;
+ }
/*
/**********************************************************
diff --git a/src/main/java/com/fasterxml/jackson/core/type/WritableTypeId.java b/src/main/java/com/fasterxml/jackson/core/type/WritableTypeId.java
index 7a52a1c..b7b9d74 100644
--- a/src/main/java/com/fasterxml/jackson/core/type/WritableTypeId.java
+++ b/src/main/java/com/fasterxml/jackson/core/type/WritableTypeId.java
@@ -48,9 +48,9 @@
WRAPPER_OBJECT,
/**
- * Inclusion as a property within Object to write (in case value is output
- * as Object); but if format has distinction between data, metadata, will
- * use latter (for example: attributes in XML) instead of data (XML elements).
+ * Inclusion as a property within Object to write, but logically as separate
+ * metadata that is not exposed as payload to caller: that is, does not match
+ * any of visible properties value object has.
*<p>
* NOTE: if shape of typed value to write is NOT Object, will instead use
* {@link #WRAPPER_ARRAY} inclusion.
@@ -60,12 +60,13 @@
METADATA_PROPERTY,
/**
- * Inclusion as a property within Object to write (in case value is output
- * as Object); but if format has distinction between data, metadata, will
- * use formetr (for example: Element in XML) instead of metadata (XML attribute).
- * In addition, it is possible that in some cases databinding may omit calling
- * type id writes for this case, and write them: if so, it will have to use
- * regular property write methods.
+ * Inclusion as a "regular" property within Object to write; this implies that
+ * its value should come from regular POJO property on serialization, and
+ * be deserialized into such property. This handling, however, is up to databinding.
+ *<p>
+ * Regarding handling, type id is ONLY written as native type id; if no native
+ * type ids available, caller is assumed to handle output some other way.
+ * This is different from {@link #METADATA_PROPERTY}.
*<p>
* NOTE: if shape of typed value to write is NOT Object, will instead use
* {@link #WRAPPER_ARRAY} inclusion.
@@ -88,7 +89,11 @@
*<p>
* Corresponds to <code>JsonTypeInfo.As.EXTERNAL_PROPERTY</code>.
*/
- PARENT_PROPERTY,
+ PARENT_PROPERTY;
+
+ public boolean requiresObjectContext() {
+ return (this == METADATA_PROPERTY) || (this == PAYLOAD_PROPERTY);
+ }
}
/**
@@ -137,6 +142,12 @@
public JsonToken valueShape;
/**
+ * Flag that can be set to indicate that wrapper structure was written (during
+ * prefix-writing); used to determine if suffix requires matching close markers.
+ */
+ public boolean wrapperWritten;
+
+ /**
* Optional additional information that generator may add during "prefix write",
* to be available on matching "suffix write".
*/