Skip to content

Commit 05a4770

Browse files
authored
Add requireTypeIdForSubtypes property for JsonTypeInfo.Value, backporting from Jackson 3.0 (#229)
1 parent c4dec4b commit 05a4770

File tree

2 files changed

+294
-0
lines changed

2 files changed

+294
-0
lines changed

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

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,4 +339,195 @@ public abstract static class None {}
339339
* @since 2.16
340340
*/
341341
public OptBoolean requireTypeIdForSubtypes() default OptBoolean.DEFAULT;
342+
343+
/*
344+
/**********************************************************************
345+
/* Value class used to enclose information, allow for
346+
/* merging of layered configuration settings.
347+
/**********************************************************************
348+
*/
349+
350+
public static class Value
351+
implements JacksonAnnotationValue<JsonTypeInfo>,
352+
java.io.Serializable
353+
{
354+
private static final long serialVersionUID = 1L;
355+
356+
// should not really be needed usually but make sure defalts to `NONE`; other
357+
// values of less interest
358+
protected final static Value EMPTY = new Value(Id.NONE, As.PROPERTY, null, null, false, null);
359+
360+
protected final Id _idType;
361+
protected final As _inclusionType;
362+
protected final String _propertyName;
363+
364+
protected final Class<?> _defaultImpl;
365+
protected final boolean _idVisible;
366+
protected final Boolean _requireTypeIdForSubtypes;
367+
368+
/*
369+
/**********************************************************************
370+
/* Construction
371+
/**********************************************************************
372+
*/
373+
374+
protected Value(Id idType, As inclusionType,
375+
String propertyName, Class<?> defaultImpl, boolean idVisible, Boolean requireTypeIdForSubtypes)
376+
{
377+
_defaultImpl = defaultImpl;
378+
_idType = idType;
379+
_inclusionType = inclusionType;
380+
_propertyName = propertyName;
381+
_idVisible = idVisible;
382+
_requireTypeIdForSubtypes = requireTypeIdForSubtypes;
383+
}
384+
385+
public static Value construct(Id idType, As inclusionType,
386+
String propertyName, Class<?> defaultImpl, boolean idVisible, Boolean requireTypeIdForSubtypes)
387+
{
388+
// couple of overrides we need to apply here. First: if no propertyName specified,
389+
// use Id-specific property name
390+
if ((propertyName == null) || propertyName.isEmpty()) {
391+
if (idType != null) {
392+
propertyName = idType.getDefaultPropertyName();
393+
} else {
394+
propertyName = "";
395+
}
396+
}
397+
// Although we can not do much here for special handling of `Void`, we can convert
398+
// annotation types as `null` (== no default implementation)
399+
if ((defaultImpl == null) || defaultImpl.isAnnotation()) {
400+
defaultImpl = null;
401+
}
402+
return new Value(idType, inclusionType, propertyName, defaultImpl, idVisible, requireTypeIdForSubtypes);
403+
}
404+
405+
public static Value from(JsonTypeInfo src) {
406+
if (src == null) {
407+
return null;
408+
}
409+
return construct(src.use(), src.include(),
410+
src.property(), src.defaultImpl(), src.visible(), src.requireTypeIdForSubtypes().asBoolean());
411+
}
412+
413+
/*
414+
/**********************************************************************
415+
/* Mutators
416+
/**********************************************************************
417+
*/
418+
419+
public Value withDefaultImpl(Class<?> impl) {
420+
return (impl == _defaultImpl) ? this :
421+
new Value(_idType, _inclusionType, _propertyName, impl, _idVisible, _requireTypeIdForSubtypes);
422+
}
423+
424+
public Value withIdType(Id idType) {
425+
return (idType == _idType) ? this :
426+
new Value(idType, _inclusionType, _propertyName, _defaultImpl, _idVisible, _requireTypeIdForSubtypes);
427+
}
428+
429+
public Value withInclusionType(As inclusionType) {
430+
return (inclusionType == _inclusionType) ? this :
431+
new Value(_idType, inclusionType, _propertyName, _defaultImpl, _idVisible, _requireTypeIdForSubtypes);
432+
}
433+
434+
public Value withPropertyName(String propName) {
435+
return (propName == _propertyName) ? this :
436+
new Value(_idType, _inclusionType, propName, _defaultImpl, _idVisible, _requireTypeIdForSubtypes);
437+
}
438+
439+
public Value withIdVisible(boolean visible) {
440+
return (visible == _idVisible) ? this :
441+
new Value(_idType, _inclusionType, _propertyName, _defaultImpl, visible, _requireTypeIdForSubtypes);
442+
}
443+
444+
public Value withRequireTypeIdForSubtypes(Boolean requireTypeIdForSubtypes) {
445+
return (_requireTypeIdForSubtypes == requireTypeIdForSubtypes) ? this :
446+
new Value(_idType, _inclusionType, _propertyName, _defaultImpl, _idVisible, requireTypeIdForSubtypes);
447+
}
448+
449+
/*
450+
/**********************************************************************
451+
/* Simple accessors
452+
/**********************************************************************
453+
*/
454+
455+
@Override
456+
public Class<JsonTypeInfo> valueFor() {
457+
return JsonTypeInfo.class;
458+
}
459+
460+
public Class<?> getDefaultImpl() { return _defaultImpl; }
461+
public Id getIdType() { return _idType; }
462+
public As getInclusionType() { return _inclusionType; }
463+
public String getPropertyName() { return _propertyName; }
464+
public boolean getIdVisible() { return _idVisible; }
465+
public Boolean getRequireTypeIdForSubtypes() { return _requireTypeIdForSubtypes; }
466+
467+
/**
468+
* Static helper method for simple(r) checking of whether there's a Value instance
469+
* that indicates that polymorphic handling is (to be) enabled.
470+
*/
471+
public static boolean isEnabled(JsonTypeInfo.Value v) {
472+
return (v != null) &&
473+
(v._idType != null) && (v._idType != Id.NONE);
474+
}
475+
476+
/*
477+
/**********************************************************************
478+
/* Standard methods
479+
/**********************************************************************
480+
*/
481+
482+
@Override
483+
public String toString() {
484+
return String.format("JsonTypeInfo.Value(idType=%s,includeAs=%s,propertyName=%s,defaultImpl=%s,idVisible=%s"
485+
+ ",requireTypeIdForSubtypes=%s)",
486+
_idType, _inclusionType, _propertyName,
487+
((_defaultImpl == null) ? "NULL" : _defaultImpl.getName()),
488+
_idVisible, _requireTypeIdForSubtypes);
489+
}
490+
491+
@Override
492+
public int hashCode() {
493+
int hashCode = 1;
494+
hashCode = 31 * hashCode + (_idType != null ? _idType.hashCode() : 0);
495+
hashCode = 31 * hashCode + (_inclusionType != null ? _inclusionType.hashCode() : 0);
496+
hashCode = 31 * hashCode + (_propertyName != null ? _propertyName.hashCode() : 0);
497+
hashCode = 31 * hashCode + (_defaultImpl != null ? _defaultImpl.hashCode() : 0);
498+
hashCode = 31 * hashCode + (_requireTypeIdForSubtypes ? 11 : -17);
499+
hashCode = 31 * hashCode + (_idVisible ? 11 : -17);
500+
return hashCode;
501+
}
502+
503+
@Override
504+
public boolean equals(Object o) {
505+
if (o == this) return true;
506+
if (o == null) return false;
507+
return (o.getClass() == getClass())
508+
&& _equals(this, (Value) o);
509+
}
510+
511+
private static boolean _equals(Value a, Value b)
512+
{
513+
return (a._idType == b._idType)
514+
&& (a._inclusionType == b._inclusionType)
515+
&& (a._defaultImpl == b._defaultImpl)
516+
&& (a._idVisible == b._idVisible)
517+
&& _equal(a._propertyName, b._propertyName)
518+
&& _equal(a._requireTypeIdForSubtypes, b._requireTypeIdForSubtypes)
519+
;
520+
}
521+
522+
private static <T> boolean _equal(T value1, T value2)
523+
{
524+
if (value1 == null) {
525+
return (value2 == null);
526+
}
527+
if (value2 == null) {
528+
return false;
529+
}
530+
return value1.equals(value2);
531+
}
532+
}
342533
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.fasterxml.jackson.annotation;
2+
3+
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
4+
5+
public class JsonTypeInfoTest extends TestBase
6+
{
7+
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, visible=true,
8+
defaultImpl = JsonTypeInfo.class, requireTypeIdForSubtypes = OptBoolean.TRUE)
9+
private final static class Anno1 { }
10+
11+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.EXTERNAL_PROPERTY,
12+
property = "ext",
13+
defaultImpl = Void.class, requireTypeIdForSubtypes = OptBoolean.FALSE)
14+
private final static class Anno2 { }
15+
16+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.EXTERNAL_PROPERTY,
17+
property = "ext",
18+
defaultImpl = Void.class)
19+
private final static class Anno3 { }
20+
21+
public void testEmpty() {
22+
// 07-Mar-2017, tatu: Important to distinguish "none" from 'empty' value...
23+
assertNull(JsonTypeInfo.Value.from(null));
24+
}
25+
26+
public void testFromAnnotation() throws Exception
27+
{
28+
JsonTypeInfo.Value v1 = JsonTypeInfo.Value.from(Anno1.class.getAnnotation(JsonTypeInfo.class));
29+
assertEquals(JsonTypeInfo.Id.CLASS, v1.getIdType());
30+
// default from annotation definition
31+
assertEquals(JsonTypeInfo.As.PROPERTY, v1.getInclusionType());
32+
// default from annotation definition
33+
assertEquals("@class", v1.getPropertyName());
34+
assertTrue(v1.getIdVisible());
35+
assertNull(v1.getDefaultImpl());
36+
assertTrue(v1.getRequireTypeIdForSubtypes());
37+
38+
JsonTypeInfo.Value v2 = JsonTypeInfo.Value.from(Anno2.class.getAnnotation(JsonTypeInfo.class));
39+
assertEquals(JsonTypeInfo.Id.NAME, v2.getIdType());
40+
assertEquals(JsonTypeInfo.As.EXTERNAL_PROPERTY, v2.getInclusionType());
41+
assertEquals("ext", v2.getPropertyName());
42+
assertFalse(v2.getIdVisible());
43+
assertEquals(Void.class, v2.getDefaultImpl());
44+
assertFalse(v2.getRequireTypeIdForSubtypes());
45+
46+
assertTrue(v1.equals(v1));
47+
assertTrue(v2.equals(v2));
48+
49+
assertFalse(v1.equals(v2));
50+
assertFalse(v2.equals(v1));
51+
52+
assertEquals("JsonTypeInfo.Value(idType=CLASS,includeAs=PROPERTY,propertyName=@class,defaultImpl=NULL,idVisible=true,requireTypeIdForSubtypes=true)", v1.toString());
53+
assertEquals("JsonTypeInfo.Value(idType=NAME,includeAs=EXTERNAL_PROPERTY,propertyName=ext,defaultImpl=java.lang.Void,idVisible=false,requireTypeIdForSubtypes=false)", v2.toString());
54+
}
55+
56+
public void testMutators() throws Exception
57+
{
58+
JsonTypeInfo.Value v = JsonTypeInfo.Value.from(Anno1.class.getAnnotation(JsonTypeInfo.class));
59+
assertEquals(JsonTypeInfo.Id.CLASS, v.getIdType());
60+
61+
assertSame(v, v.withIdType(JsonTypeInfo.Id.CLASS));
62+
JsonTypeInfo.Value v2 = v.withIdType(JsonTypeInfo.Id.MINIMAL_CLASS);
63+
assertEquals(JsonTypeInfo.Id.MINIMAL_CLASS, v2.getIdType());
64+
65+
assertEquals(JsonTypeInfo.As.PROPERTY, v.getInclusionType());
66+
assertSame(v, v.withInclusionType(JsonTypeInfo.As.PROPERTY));
67+
v2 = v.withInclusionType(JsonTypeInfo.As.EXTERNAL_PROPERTY);
68+
assertEquals(JsonTypeInfo.As.EXTERNAL_PROPERTY, v2.getInclusionType());
69+
70+
assertSame(v, v.withDefaultImpl(null));
71+
v2 = v.withDefaultImpl(String.class);
72+
assertEquals(String.class, v2.getDefaultImpl());
73+
74+
assertSame(v, v.withIdVisible(true));
75+
assertFalse(v.withIdVisible(false).getIdVisible());
76+
77+
assertEquals("foobar", v.withPropertyName("foobar").getPropertyName());
78+
}
79+
80+
public void testWithRequireTypeIdForSubtypes() {
81+
JsonTypeInfo.Value empty = JsonTypeInfo.Value.EMPTY;
82+
assertNull(empty.getRequireTypeIdForSubtypes());
83+
84+
JsonTypeInfo.Value requireTypeIdTrue = empty.withRequireTypeIdForSubtypes(Boolean.TRUE);
85+
assertEquals(Boolean.TRUE, requireTypeIdTrue.getRequireTypeIdForSubtypes());
86+
87+
JsonTypeInfo.Value requireTypeIdFalse = empty.withRequireTypeIdForSubtypes(Boolean.FALSE);
88+
assertEquals(Boolean.FALSE, requireTypeIdFalse.getRequireTypeIdForSubtypes());
89+
90+
JsonTypeInfo.Value requireTypeIdDefault = empty.withRequireTypeIdForSubtypes(null);
91+
assertNull(requireTypeIdDefault.getRequireTypeIdForSubtypes());
92+
}
93+
94+
public void testDefaultValueForRequireTypeIdForSubtypes() {
95+
// default value
96+
JsonTypeInfo.Value v3 = JsonTypeInfo.Value.from(Anno3.class.getAnnotation(JsonTypeInfo.class));
97+
assertNull(v3.getRequireTypeIdForSubtypes());
98+
99+
// toString()
100+
assertEquals("JsonTypeInfo.Value(idType=NAME,includeAs=EXTERNAL_PROPERTY,propertyName=ext,"
101+
+ "defaultImpl=java.lang.Void,idVisible=false,requireTypeIdForSubtypes=null)", v3.toString());
102+
}
103+
}

0 commit comments

Comments
 (0)