diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x
index 7b459e08..05147890 100644
--- a/release-notes/VERSION-2.x
+++ b/release-notes/VERSION-2.x
@@ -17,6 +17,8 @@ NOTE: Jackson 3.x components rely on 2.x annotations; there are no separate
2.22 (not yet released)
#339: Add `OptBoolean` valued property "order" in `@JsonIncludeProperties`
+#342: Add `@JsonTypeInfo.writeTypeIdForDefaultImpl` to allow skipping
+ writing of type id for values of default type
2.21 (18-Jan-2026)
diff --git a/src/main/java/com/fasterxml/jackson/annotation/JsonTypeInfo.java b/src/main/java/com/fasterxml/jackson/annotation/JsonTypeInfo.java
index cdb9167d..0f7a2716 100644
--- a/src/main/java/com/fasterxml/jackson/annotation/JsonTypeInfo.java
+++ b/src/main/java/com/fasterxml/jackson/annotation/JsonTypeInfo.java
@@ -67,9 +67,9 @@
public @interface JsonTypeInfo
{
/*
- /**********************************************************
+ /**********************************************************************
/* Value enumerations used for properties
- /**********************************************************
+ /**********************************************************************
*/
/**
@@ -256,9 +256,9 @@ public enum As {
}
/*
- /**********************************************************
+ /**********************************************************************
/* Annotation properties
- /**********************************************************
+ /**********************************************************************
*/
/**
@@ -339,23 +339,6 @@ public enum As {
*/
public boolean visible() default false;
- /*
- /**********************************************************
- /* Helper classes
- /**********************************************************
- */
-
- /**
- * This marker class that is only to be used with defaultImpl
- * annotation property, to indicate that there is no default implementation
- * specified.
- *
- * @deprecated Since 2.5, use any Annotation type (such as {@link JsonTypeInfo}),
- * if such behavior is needed; this is rarely necessary.
- */
- @Deprecated // since 2.5
- public abstract static class None {}
-
/**
* Specifies whether the type ID should be strictly required during polymorphic
* deserialization of its subtypes.
@@ -374,6 +357,44 @@ public abstract static class None {}
*/
public OptBoolean requireTypeIdForSubtypes() default OptBoolean.DEFAULT;
+ /**
+ * Property that defines whether serialization of type id should be done
+ * when the runtime type of the value is the same as {@link #defaultImpl()}.
+ * Skipping write can be useful since during deserialization, if no type id is present,
+ * {@code defaultImpl} is used as the fallback type -- so the type id is redundant
+ * for that type.
+ *
+ * When disabled ({@link OptBoolean#FALSE}), the type id will NOT be written + * if the actual runtime class of the value exactly matches {@code defaultImpl}. + * Subclasses of {@code defaultImpl} will still have their type id written. + *
+ * NOTE: support for this feature is only added in Jackson 3.x, specifically + * 3.2. It is not supported by Jackson 2.x. + *
+ * Default value is {@link OptBoolean#DEFAULT} (which means {@code TRUE}),
+ * preserving backwards-compatible behavior of always writing type id.
+ *
+ * @since 2.22
+ */
+ public OptBoolean writeTypeIdForDefaultImpl() default OptBoolean.DEFAULT;
+
+ /*
+ /**********************************************************************
+ /* Helper classes
+ /**********************************************************************
+ */
+
+ /**
+ * This marker class that is only to be used with defaultImpl
+ * annotation property, to indicate that there is no default implementation
+ * specified.
+ *
+ * @deprecated Since 2.5, use any Annotation type (such as {@link JsonTypeInfo}),
+ * if such behavior is needed; this is rarely necessary.
+ */
+ @Deprecated // since 2.5
+ public abstract static class None {}
+
/*
/**********************************************************************
/* Value class used to enclose information, allow for
@@ -399,14 +420,23 @@ public static class Value
protected final boolean _idVisible;
protected final Boolean _requireTypeIdForSubtypes;
+ /**
+ * @since 2.22
+ */
+ protected final Boolean _writeTypeIdForDefaultImpl;
+
/*
/**********************************************************************
/* Construction
/**********************************************************************
*/
+ /**
+ * @since 2.22
+ */
protected Value(Id idType, As inclusionType,
- String propertyName, Class> defaultImpl, boolean idVisible, Boolean requireTypeIdForSubtypes)
+ String propertyName, Class> defaultImpl, boolean idVisible,
+ Boolean requireTypeIdForSubtypes, Boolean writeTypeIdForDefaultImpl)
{
_defaultImpl = defaultImpl;
_idType = idType;
@@ -414,10 +444,25 @@ protected Value(Id idType, As inclusionType,
_propertyName = propertyName;
_idVisible = idVisible;
_requireTypeIdForSubtypes = requireTypeIdForSubtypes;
+ _writeTypeIdForDefaultImpl = writeTypeIdForDefaultImpl;
}
- public static Value construct(Id idType, As inclusionType,
+ /**
+ * @deprecated Since 2.22 use the 7-argument overload
+ */
+ @Deprecated
+ protected Value(Id idType, As inclusionType,
String propertyName, Class> defaultImpl, boolean idVisible, Boolean requireTypeIdForSubtypes)
+ {
+ this(idType, inclusionType, propertyName, defaultImpl, idVisible, requireTypeIdForSubtypes, null);
+ }
+
+ /**
+ * @since 2.22
+ */
+ public static Value construct(Id idType, As inclusionType,
+ String propertyName, Class> defaultImpl, boolean idVisible,
+ Boolean requireTypeIdForSubtypes, Boolean writeTypeIdForDefaultImpl)
{
// couple of overrides we need to apply here. First: if no propertyName specified,
// use Id-specific property name
@@ -433,7 +478,19 @@ public static Value construct(Id idType, As inclusionType,
if ((defaultImpl == null) || defaultImpl.isAnnotation()) {
defaultImpl = null;
}
- return new Value(idType, inclusionType, propertyName, defaultImpl, idVisible, requireTypeIdForSubtypes);
+ return new Value(idType, inclusionType, propertyName, defaultImpl, idVisible,
+ requireTypeIdForSubtypes, writeTypeIdForDefaultImpl);
+ }
+
+ /**
+ * @deprecated Since 2.22 use the 7-argument overload
+ */
+ @Deprecated
+ public static Value construct(Id idType, As inclusionType,
+ String propertyName, Class> defaultImpl, boolean idVisible,
+ Boolean requireTypeIdForSubtypes)
+ {
+ return construct(idType, inclusionType, propertyName, defaultImpl, idVisible, requireTypeIdForSubtypes, null);
}
public static Value from(JsonTypeInfo src) {
@@ -441,7 +498,9 @@ public static Value from(JsonTypeInfo src) {
return null;
}
return construct(src.use(), src.include(),
- src.property(), src.defaultImpl(), src.visible(), src.requireTypeIdForSubtypes().asBoolean());
+ src.property(), src.defaultImpl(), src.visible(),
+ src.requireTypeIdForSubtypes().asBoolean(),
+ src.writeTypeIdForDefaultImpl().asBoolean());
}
/*
@@ -452,32 +511,47 @@ public static Value from(JsonTypeInfo src) {
public Value withDefaultImpl(Class> impl) {
return (impl == _defaultImpl) ? this :
- new Value(_idType, _inclusionType, _propertyName, impl, _idVisible, _requireTypeIdForSubtypes);
+ new Value(_idType, _inclusionType, _propertyName, impl, _idVisible,
+ _requireTypeIdForSubtypes, _writeTypeIdForDefaultImpl);
}
public Value withIdType(Id idType) {
return (idType == _idType) ? this :
- new Value(idType, _inclusionType, _propertyName, _defaultImpl, _idVisible, _requireTypeIdForSubtypes);
+ new Value(idType, _inclusionType, _propertyName, _defaultImpl, _idVisible,
+ _requireTypeIdForSubtypes, _writeTypeIdForDefaultImpl);
}
public Value withInclusionType(As inclusionType) {
return (inclusionType == _inclusionType) ? this :
- new Value(_idType, inclusionType, _propertyName, _defaultImpl, _idVisible, _requireTypeIdForSubtypes);
+ new Value(_idType, inclusionType, _propertyName, _defaultImpl, _idVisible,
+ _requireTypeIdForSubtypes, _writeTypeIdForDefaultImpl);
}
public Value withPropertyName(String propName) {
return (propName == _propertyName) ? this :
- new Value(_idType, _inclusionType, propName, _defaultImpl, _idVisible, _requireTypeIdForSubtypes);
+ new Value(_idType, _inclusionType, propName, _defaultImpl, _idVisible,
+ _requireTypeIdForSubtypes, _writeTypeIdForDefaultImpl);
}
public Value withIdVisible(boolean visible) {
return (visible == _idVisible) ? this :
- new Value(_idType, _inclusionType, _propertyName, _defaultImpl, visible, _requireTypeIdForSubtypes);
+ new Value(_idType, _inclusionType, _propertyName, _defaultImpl, visible,
+ _requireTypeIdForSubtypes, _writeTypeIdForDefaultImpl);
}
-
+
public Value withRequireTypeIdForSubtypes(Boolean requireTypeIdForSubtypes) {
return (_requireTypeIdForSubtypes == requireTypeIdForSubtypes) ? this :
- new Value(_idType, _inclusionType, _propertyName, _defaultImpl, _idVisible, requireTypeIdForSubtypes);
+ new Value(_idType, _inclusionType, _propertyName, _defaultImpl, _idVisible,
+ requireTypeIdForSubtypes, _writeTypeIdForDefaultImpl);
+ }
+
+ /**
+ * @since 2.22
+ */
+ public Value withWriteTypeIdForDefaultImpl(Boolean writeTypeIdForDefaultImpl) {
+ return (_writeTypeIdForDefaultImpl == writeTypeIdForDefaultImpl) ? this :
+ new Value(_idType, _inclusionType, _propertyName, _defaultImpl, _idVisible,
+ _requireTypeIdForSubtypes, writeTypeIdForDefaultImpl);
}
/*
@@ -498,6 +572,18 @@ public Class