Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
160 changes: 124 additions & 36 deletions src/main/java/com/fasterxml/jackson/annotation/JsonTypeInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@
public @interface JsonTypeInfo
{
/*
/**********************************************************
/**********************************************************************
/* Value enumerations used for properties
/**********************************************************
/**********************************************************************
*/

/**
Expand Down Expand Up @@ -256,9 +256,9 @@ public enum As {
}

/*
/**********************************************************
/**********************************************************************
/* Annotation properties
/**********************************************************
/**********************************************************************
*/

/**
Expand Down Expand Up @@ -339,23 +339,6 @@ public enum As {
*/
public boolean visible() default false;

/*
/**********************************************************
/* Helper classes
/**********************************************************
*/

/**
* This marker class that is only to be used with <code>defaultImpl</code>
* 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.
Expand All @@ -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.
*<p>
* 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.
*<p>
* NOTE: support for this feature is only added in Jackson 3.x, specifically
* 3.2. It is not supported by Jackson 2.x.
*<p>
* 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 <code>defaultImpl</code>
* 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
Expand All @@ -399,25 +420,49 @@ 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;
_inclusionType = 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
Expand All @@ -433,15 +478,29 @@ 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) {
if (src == null) {
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());
}

/*
Expand All @@ -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);
}

/*
Expand All @@ -498,6 +572,18 @@ public Class<JsonTypeInfo> valueFor() {
public boolean getIdVisible() { return _idVisible; }
public Boolean getRequireTypeIdForSubtypes() { return _requireTypeIdForSubtypes; }

/**
* @since 2.22
*/
public Boolean getWriteTypeIdForDefaultImpl() { return _writeTypeIdForDefaultImpl; }

/**
* @since 2.22
*/
public boolean shouldWriteTypeIdForDefaultImpl() {
return (_writeTypeIdForDefaultImpl == null) || _writeTypeIdForDefaultImpl.booleanValue();
}

/**
* Static helper method for simple(r) checking of whether there's a Value instance
* that indicates that polymorphic handling is (to be) enabled.
Expand All @@ -516,11 +602,11 @@ public static boolean isEnabled(JsonTypeInfo.Value v) {

@Override
public String toString() {
return String.format("JsonTypeInfo.Value(idType=%s,includeAs=%s,propertyName=%s,defaultImpl=%s,idVisible=%s"
+ ",requireTypeIdForSubtypes=%s)",
return String.format("JsonTypeInfo.Value(idType=%s,includeAs=%s,propertyName=%s,defaultImpl=%s,idVisible=%s"
+ ",requireTypeIdForSubtypes=%s,writeTypeIdForDefaultImpl=%s)",
_idType, _inclusionType, _propertyName,
((_defaultImpl == null) ? "NULL" : _defaultImpl.getName()),
_idVisible, _requireTypeIdForSubtypes);
_idVisible, _requireTypeIdForSubtypes, _writeTypeIdForDefaultImpl);
}

@Override
Expand All @@ -530,8 +616,9 @@ public int hashCode() {
hashCode = 31 * hashCode + (_inclusionType != null ? _inclusionType.hashCode() : 0);
hashCode = 31 * hashCode + (_propertyName != null ? _propertyName.hashCode() : 0);
hashCode = 31 * hashCode + (_defaultImpl != null ? _defaultImpl.hashCode() : 0);
hashCode = 31 * hashCode + (_requireTypeIdForSubtypes ? 11 : -17);
hashCode = 31 * hashCode + Objects.hashCode(_requireTypeIdForSubtypes);
hashCode = 31 * hashCode + (_idVisible ? 11 : -17);
hashCode = 31 * hashCode + Objects.hashCode(_writeTypeIdForDefaultImpl);
return hashCode;
}

Expand All @@ -551,6 +638,7 @@ private static boolean _equals(Value a, Value b)
&& (a._idVisible == b._idVisible)
&& Objects.equals(a._propertyName, b._propertyName)
&& Objects.equals(a._requireTypeIdForSubtypes, b._requireTypeIdForSubtypes)
&& Objects.equals(a._writeTypeIdForDefaultImpl, b._writeTypeIdForDefaultImpl)
;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ public void testWithOverridesAll() {
@Test
public void testWithOverridesEmpty() {
JsonIncludeProperties.Value v = JsonIncludeProperties.Value.from(Bogus.class.getAnnotation(JsonIncludeProperties.class));
v = v.withOverrides(new JsonIncludeProperties.Value(Collections.<String>emptySet()));
v = v.withOverrides(new JsonIncludeProperties.Value(Collections.<String>emptySet(), false));
Set<String> included = v.getIncluded();
assertEquals(0, included.size());
}

@Test
public void testWithOverridesMerge() {
JsonIncludeProperties.Value v = JsonIncludeProperties.Value.from(Bogus.class.getAnnotation(JsonIncludeProperties.class));
v = v.withOverrides(new JsonIncludeProperties.Value(_set("foo")));
v = v.withOverrides(new JsonIncludeProperties.Value(_set("foo"), false));
Set<String> included = v.getIncluded();
assertEquals(1, included.size());
assertEquals(_set("foo"), included);
Expand Down
Loading