Skip to content

Commit 5731cfe

Browse files
authored
Add @JsonTypeInfo.writeTypeIdForDefaultImpl to allow skipping writing of type id for values of default type (#342)
1 parent fc9c6ab commit 5731cfe

File tree

4 files changed

+234
-42
lines changed

4 files changed

+234
-42
lines changed

release-notes/VERSION-2.x

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ NOTE: Jackson 3.x components rely on 2.x annotations; there are no separate
1717
2.22 (not yet released)
1818

1919
#339: Add `OptBoolean` valued property "order" in `@JsonIncludeProperties`
20+
#342: Add `@JsonTypeInfo.writeTypeIdForDefaultImpl` to allow skipping
21+
writing of type id for values of default type
2022

2123
2.21 (18-Jan-2026)
2224

src/main/java/com/fasterxml/jackson/annotation/JsonTypeInfo.java

Lines changed: 124 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@
6767
public @interface JsonTypeInfo
6868
{
6969
/*
70-
/**********************************************************
70+
/**********************************************************************
7171
/* Value enumerations used for properties
72-
/**********************************************************
72+
/**********************************************************************
7373
*/
7474

7575
/**
@@ -256,9 +256,9 @@ public enum As {
256256
}
257257

258258
/*
259-
/**********************************************************
259+
/**********************************************************************
260260
/* Annotation properties
261-
/**********************************************************
261+
/**********************************************************************
262262
*/
263263

264264
/**
@@ -339,23 +339,6 @@ public enum As {
339339
*/
340340
public boolean visible() default false;
341341

342-
/*
343-
/**********************************************************
344-
/* Helper classes
345-
/**********************************************************
346-
*/
347-
348-
/**
349-
* This marker class that is only to be used with <code>defaultImpl</code>
350-
* annotation property, to indicate that there is no default implementation
351-
* specified.
352-
*
353-
* @deprecated Since 2.5, use any Annotation type (such as {@link JsonTypeInfo}),
354-
* if such behavior is needed; this is rarely necessary.
355-
*/
356-
@Deprecated // since 2.5
357-
public abstract static class None {}
358-
359342
/**
360343
* Specifies whether the type ID should be strictly required during polymorphic
361344
* deserialization of its subtypes.
@@ -374,6 +357,44 @@ public abstract static class None {}
374357
*/
375358
public OptBoolean requireTypeIdForSubtypes() default OptBoolean.DEFAULT;
376359

360+
/**
361+
* Property that defines whether serialization of type id should be done
362+
* when the runtime type of the value is the same as {@link #defaultImpl()}.
363+
* Skipping write can be useful since during deserialization, if no type id is present,
364+
* {@code defaultImpl} is used as the fallback type -- so the type id is redundant
365+
* for that type.
366+
*<p>
367+
* When disabled ({@link OptBoolean#FALSE}), the type id will NOT be written
368+
* if the actual runtime class of the value exactly matches {@code defaultImpl}.
369+
* Subclasses of {@code defaultImpl} will still have their type id written.
370+
*<p>
371+
* NOTE: support for this feature is only added in Jackson 3.x, specifically
372+
* 3.2. It is not supported by Jackson 2.x.
373+
*<p>
374+
* Default value is {@link OptBoolean#DEFAULT} (which means {@code TRUE}),
375+
* preserving backwards-compatible behavior of always writing type id.
376+
*
377+
* @since 2.22
378+
*/
379+
public OptBoolean writeTypeIdForDefaultImpl() default OptBoolean.DEFAULT;
380+
381+
/*
382+
/**********************************************************************
383+
/* Helper classes
384+
/**********************************************************************
385+
*/
386+
387+
/**
388+
* This marker class that is only to be used with <code>defaultImpl</code>
389+
* annotation property, to indicate that there is no default implementation
390+
* specified.
391+
*
392+
* @deprecated Since 2.5, use any Annotation type (such as {@link JsonTypeInfo}),
393+
* if such behavior is needed; this is rarely necessary.
394+
*/
395+
@Deprecated // since 2.5
396+
public abstract static class None {}
397+
377398
/*
378399
/**********************************************************************
379400
/* Value class used to enclose information, allow for
@@ -399,25 +420,49 @@ public static class Value
399420
protected final boolean _idVisible;
400421
protected final Boolean _requireTypeIdForSubtypes;
401422

423+
/**
424+
* @since 2.22
425+
*/
426+
protected final Boolean _writeTypeIdForDefaultImpl;
427+
402428
/*
403429
/**********************************************************************
404430
/* Construction
405431
/**********************************************************************
406432
*/
407433

434+
/**
435+
* @since 2.22
436+
*/
408437
protected Value(Id idType, As inclusionType,
409-
String propertyName, Class<?> defaultImpl, boolean idVisible, Boolean requireTypeIdForSubtypes)
438+
String propertyName, Class<?> defaultImpl, boolean idVisible,
439+
Boolean requireTypeIdForSubtypes, Boolean writeTypeIdForDefaultImpl)
410440
{
411441
_defaultImpl = defaultImpl;
412442
_idType = idType;
413443
_inclusionType = inclusionType;
414444
_propertyName = propertyName;
415445
_idVisible = idVisible;
416446
_requireTypeIdForSubtypes = requireTypeIdForSubtypes;
447+
_writeTypeIdForDefaultImpl = writeTypeIdForDefaultImpl;
417448
}
418449

419-
public static Value construct(Id idType, As inclusionType,
450+
/**
451+
* @deprecated Since 2.22 use the 7-argument overload
452+
*/
453+
@Deprecated
454+
protected Value(Id idType, As inclusionType,
420455
String propertyName, Class<?> defaultImpl, boolean idVisible, Boolean requireTypeIdForSubtypes)
456+
{
457+
this(idType, inclusionType, propertyName, defaultImpl, idVisible, requireTypeIdForSubtypes, null);
458+
}
459+
460+
/**
461+
* @since 2.22
462+
*/
463+
public static Value construct(Id idType, As inclusionType,
464+
String propertyName, Class<?> defaultImpl, boolean idVisible,
465+
Boolean requireTypeIdForSubtypes, Boolean writeTypeIdForDefaultImpl)
421466
{
422467
// couple of overrides we need to apply here. First: if no propertyName specified,
423468
// use Id-specific property name
@@ -433,15 +478,29 @@ public static Value construct(Id idType, As inclusionType,
433478
if ((defaultImpl == null) || defaultImpl.isAnnotation()) {
434479
defaultImpl = null;
435480
}
436-
return new Value(idType, inclusionType, propertyName, defaultImpl, idVisible, requireTypeIdForSubtypes);
481+
return new Value(idType, inclusionType, propertyName, defaultImpl, idVisible,
482+
requireTypeIdForSubtypes, writeTypeIdForDefaultImpl);
483+
}
484+
485+
/**
486+
* @deprecated Since 2.22 use the 7-argument overload
487+
*/
488+
@Deprecated
489+
public static Value construct(Id idType, As inclusionType,
490+
String propertyName, Class<?> defaultImpl, boolean idVisible,
491+
Boolean requireTypeIdForSubtypes)
492+
{
493+
return construct(idType, inclusionType, propertyName, defaultImpl, idVisible, requireTypeIdForSubtypes, null);
437494
}
438495

439496
public static Value from(JsonTypeInfo src) {
440497
if (src == null) {
441498
return null;
442499
}
443500
return construct(src.use(), src.include(),
444-
src.property(), src.defaultImpl(), src.visible(), src.requireTypeIdForSubtypes().asBoolean());
501+
src.property(), src.defaultImpl(), src.visible(),
502+
src.requireTypeIdForSubtypes().asBoolean(),
503+
src.writeTypeIdForDefaultImpl().asBoolean());
445504
}
446505

447506
/*
@@ -452,32 +511,47 @@ public static Value from(JsonTypeInfo src) {
452511

453512
public Value withDefaultImpl(Class<?> impl) {
454513
return (impl == _defaultImpl) ? this :
455-
new Value(_idType, _inclusionType, _propertyName, impl, _idVisible, _requireTypeIdForSubtypes);
514+
new Value(_idType, _inclusionType, _propertyName, impl, _idVisible,
515+
_requireTypeIdForSubtypes, _writeTypeIdForDefaultImpl);
456516
}
457517

458518
public Value withIdType(Id idType) {
459519
return (idType == _idType) ? this :
460-
new Value(idType, _inclusionType, _propertyName, _defaultImpl, _idVisible, _requireTypeIdForSubtypes);
520+
new Value(idType, _inclusionType, _propertyName, _defaultImpl, _idVisible,
521+
_requireTypeIdForSubtypes, _writeTypeIdForDefaultImpl);
461522
}
462523

463524
public Value withInclusionType(As inclusionType) {
464525
return (inclusionType == _inclusionType) ? this :
465-
new Value(_idType, inclusionType, _propertyName, _defaultImpl, _idVisible, _requireTypeIdForSubtypes);
526+
new Value(_idType, inclusionType, _propertyName, _defaultImpl, _idVisible,
527+
_requireTypeIdForSubtypes, _writeTypeIdForDefaultImpl);
466528
}
467529

468530
public Value withPropertyName(String propName) {
469531
return (propName == _propertyName) ? this :
470-
new Value(_idType, _inclusionType, propName, _defaultImpl, _idVisible, _requireTypeIdForSubtypes);
532+
new Value(_idType, _inclusionType, propName, _defaultImpl, _idVisible,
533+
_requireTypeIdForSubtypes, _writeTypeIdForDefaultImpl);
471534
}
472535

473536
public Value withIdVisible(boolean visible) {
474537
return (visible == _idVisible) ? this :
475-
new Value(_idType, _inclusionType, _propertyName, _defaultImpl, visible, _requireTypeIdForSubtypes);
538+
new Value(_idType, _inclusionType, _propertyName, _defaultImpl, visible,
539+
_requireTypeIdForSubtypes, _writeTypeIdForDefaultImpl);
476540
}
477-
541+
478542
public Value withRequireTypeIdForSubtypes(Boolean requireTypeIdForSubtypes) {
479543
return (_requireTypeIdForSubtypes == requireTypeIdForSubtypes) ? this :
480-
new Value(_idType, _inclusionType, _propertyName, _defaultImpl, _idVisible, requireTypeIdForSubtypes);
544+
new Value(_idType, _inclusionType, _propertyName, _defaultImpl, _idVisible,
545+
requireTypeIdForSubtypes, _writeTypeIdForDefaultImpl);
546+
}
547+
548+
/**
549+
* @since 2.22
550+
*/
551+
public Value withWriteTypeIdForDefaultImpl(Boolean writeTypeIdForDefaultImpl) {
552+
return (_writeTypeIdForDefaultImpl == writeTypeIdForDefaultImpl) ? this :
553+
new Value(_idType, _inclusionType, _propertyName, _defaultImpl, _idVisible,
554+
_requireTypeIdForSubtypes, writeTypeIdForDefaultImpl);
481555
}
482556

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

575+
/**
576+
* @since 2.22
577+
*/
578+
public Boolean getWriteTypeIdForDefaultImpl() { return _writeTypeIdForDefaultImpl; }
579+
580+
/**
581+
* @since 2.22
582+
*/
583+
public boolean shouldWriteTypeIdForDefaultImpl() {
584+
return (_writeTypeIdForDefaultImpl == null) || _writeTypeIdForDefaultImpl.booleanValue();
585+
}
586+
501587
/**
502588
* Static helper method for simple(r) checking of whether there's a Value instance
503589
* that indicates that polymorphic handling is (to be) enabled.
@@ -516,11 +602,11 @@ public static boolean isEnabled(JsonTypeInfo.Value v) {
516602

517603
@Override
518604
public String toString() {
519-
return String.format("JsonTypeInfo.Value(idType=%s,includeAs=%s,propertyName=%s,defaultImpl=%s,idVisible=%s"
520-
+ ",requireTypeIdForSubtypes=%s)",
605+
return String.format("JsonTypeInfo.Value(idType=%s,includeAs=%s,propertyName=%s,defaultImpl=%s,idVisible=%s"
606+
+ ",requireTypeIdForSubtypes=%s,writeTypeIdForDefaultImpl=%s)",
521607
_idType, _inclusionType, _propertyName,
522608
((_defaultImpl == null) ? "NULL" : _defaultImpl.getName()),
523-
_idVisible, _requireTypeIdForSubtypes);
609+
_idVisible, _requireTypeIdForSubtypes, _writeTypeIdForDefaultImpl);
524610
}
525611

526612
@Override
@@ -530,8 +616,9 @@ public int hashCode() {
530616
hashCode = 31 * hashCode + (_inclusionType != null ? _inclusionType.hashCode() : 0);
531617
hashCode = 31 * hashCode + (_propertyName != null ? _propertyName.hashCode() : 0);
532618
hashCode = 31 * hashCode + (_defaultImpl != null ? _defaultImpl.hashCode() : 0);
533-
hashCode = 31 * hashCode + (_requireTypeIdForSubtypes ? 11 : -17);
619+
hashCode = 31 * hashCode + Objects.hashCode(_requireTypeIdForSubtypes);
534620
hashCode = 31 * hashCode + (_idVisible ? 11 : -17);
621+
hashCode = 31 * hashCode + Objects.hashCode(_writeTypeIdForDefaultImpl);
535622
return hashCode;
536623
}
537624

@@ -551,6 +638,7 @@ private static boolean _equals(Value a, Value b)
551638
&& (a._idVisible == b._idVisible)
552639
&& Objects.equals(a._propertyName, b._propertyName)
553640
&& Objects.equals(a._requireTypeIdForSubtypes, b._requireTypeIdForSubtypes)
641+
&& Objects.equals(a._writeTypeIdForDefaultImpl, b._writeTypeIdForDefaultImpl)
554642
;
555643
}
556644
}

src/test/java/com/fasterxml/jackson/annotation/JsonIncludePropertiesTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,15 @@ public void testWithOverridesAll() {
7070
@Test
7171
public void testWithOverridesEmpty() {
7272
JsonIncludeProperties.Value v = JsonIncludeProperties.Value.from(Bogus.class.getAnnotation(JsonIncludeProperties.class));
73-
v = v.withOverrides(new JsonIncludeProperties.Value(Collections.<String>emptySet()));
73+
v = v.withOverrides(new JsonIncludeProperties.Value(Collections.<String>emptySet(), false));
7474
Set<String> included = v.getIncluded();
7575
assertEquals(0, included.size());
7676
}
7777

7878
@Test
7979
public void testWithOverridesMerge() {
8080
JsonIncludeProperties.Value v = JsonIncludeProperties.Value.from(Bogus.class.getAnnotation(JsonIncludeProperties.class));
81-
v = v.withOverrides(new JsonIncludeProperties.Value(_set("foo")));
81+
v = v.withOverrides(new JsonIncludeProperties.Value(_set("foo"), false));
8282
Set<String> included = v.getIncluded();
8383
assertEquals(1, included.size());
8484
assertEquals(_set("foo"), included);

0 commit comments

Comments
 (0)