Skip to content

Commit be114a9

Browse files
committed
Fix #1385
1 parent 9f01551 commit be114a9

File tree

5 files changed

+184
-61
lines changed

5 files changed

+184
-61
lines changed

release-notes/VERSION

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Project: jackson-databind
99
#1384: `@JsonDeserialize(keyUsing = ...)` does not work correctly together with
1010
DefaultTyping.NON_FINAL
1111
(reported by Oleg Z)
12+
#1385: Polymorphic type lost when using `@JsonValue`
13+
(reported by TomMarkuske@github)
1214
#1389 Problem with handling of multi-argument creator with Enums
1315
(fix contributed by Pavel P)
1416

src/main/java/com/fasterxml/jackson/databind/ser/std/JsonValueSerializer.java

Lines changed: 134 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.LinkedHashSet;
77
import java.util.Set;
88

9+
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
910
import com.fasterxml.jackson.core.*;
1011
import com.fasterxml.jackson.databind.*;
1112
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
@@ -14,6 +15,7 @@
1415
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
1516
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonStringFormatVisitor;
1617
import com.fasterxml.jackson.databind.jsonschema.SchemaAware;
18+
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
1719
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
1820
import com.fasterxml.jackson.databind.ser.BeanSerializer;
1921
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
@@ -44,7 +46,7 @@ public class JsonValueSerializer
4446
protected final JsonSerializer<Object> _valueSerializer;
4547

4648
protected final BeanProperty _property;
47-
49+
4850
/**
4951
* This is a flag that is set in rare (?) cases where this serializer
5052
* is used for "natural" types (boolean, int, String, double); and where
@@ -156,12 +158,12 @@ public JsonSerializer<?> createContextual(SerializerProvider provider,
156158
*/
157159

158160
@Override
159-
public void serialize(Object bean, JsonGenerator jgen, SerializerProvider prov) throws IOException
161+
public void serialize(Object bean, JsonGenerator gen, SerializerProvider prov) throws IOException
160162
{
161163
try {
162164
Object value = _accessorMethod.getValue(bean);
163165
if (value == null) {
164-
prov.defaultSerializeNull(jgen);
166+
prov.defaultSerializeNull(gen);
165167
return;
166168
}
167169
JsonSerializer<Object> ser = _valueSerializer;
@@ -174,7 +176,7 @@ public void serialize(Object bean, JsonGenerator jgen, SerializerProvider prov)
174176
// let's cache it, may be needed soon again
175177
ser = prov.findTypedValueSerializer(c, true, _property);
176178
}
177-
ser.serialize(value, jgen, prov);
179+
ser.serialize(value, gen, prov);
178180
} catch (IOException ioe) {
179181
throw ioe;
180182
} catch (Exception e) {
@@ -193,7 +195,7 @@ public void serialize(Object bean, JsonGenerator jgen, SerializerProvider prov)
193195
}
194196

195197
@Override
196-
public void serializeWithType(Object bean, JsonGenerator jgen, SerializerProvider provider,
198+
public void serializeWithType(Object bean, JsonGenerator gen, SerializerProvider provider,
197199
TypeSerializer typeSer0) throws IOException
198200
{
199201
// Regardless of other parts, first need to find value to serialize:
@@ -202,30 +204,29 @@ public void serializeWithType(Object bean, JsonGenerator jgen, SerializerProvide
202204
value = _accessorMethod.getValue(bean);
203205
// and if we got null, can also just write it directly
204206
if (value == null) {
205-
provider.defaultSerializeNull(jgen);
207+
provider.defaultSerializeNull(gen);
206208
return;
207209
}
208210
JsonSerializer<Object> ser = _valueSerializer;
209-
if (ser == null) { // already got a serializer? fabulous, that be easy...
211+
if (ser == null) { // no serializer yet? Need to fetch
210212
// ser = provider.findTypedValueSerializer(value.getClass(), true, _property);
211213
ser = provider.findValueSerializer(value.getClass(), _property);
212214
} else {
213215
/* 09-Dec-2010, tatu: To work around natural type's refusal to add type info, we do
214216
* this (note: type is for the wrapper type, not enclosed value!)
215217
*/
216218
if (_forceTypeInformation) {
217-
typeSer0.writeTypePrefixForScalar(bean, jgen);
218-
ser.serialize(value, jgen, provider);
219-
typeSer0.writeTypeSuffixForScalar(bean, jgen);
219+
typeSer0.writeTypePrefixForScalar(bean, gen);
220+
ser.serialize(value, gen, provider);
221+
typeSer0.writeTypeSuffixForScalar(bean, gen);
220222
return;
221223
}
222224
}
223-
/* 13-Feb-2013, tatu: Turns out that work-around should NOT be required
224-
* at all; it would not lead to correct behavior (as per #167).
225-
*/
226-
// and then redirect type id lookups
227-
// TypeSerializer typeSer = new TypeSerializerWrapper(typeSer0, bean);
228-
ser.serializeWithType(value, jgen, provider, typeSer0);
225+
// 28-Sep-2016, tatu: As per [databind#1385], we do need to do some juggling
226+
// to use different Object for type id (logical type) and actual serialization
227+
// (delegat type).
228+
TypeSerializerRerouter rr = new TypeSerializerRerouter(typeSer0, bean);
229+
ser.serializeWithType(value, gen, provider, rr);
229230
} catch (IOException ioe) {
230231
throw ioe;
231232
} catch (Exception e) {
@@ -339,7 +340,7 @@ protected boolean isNaturalTypeWithStdHandling(Class<?> rawType, JsonSerializer<
339340
}
340341
return isDefaultSerializer(ser);
341342
}
342-
343+
343344
/*
344345
/**********************************************************
345346
/* Other methods
@@ -350,4 +351,120 @@ protected boolean isNaturalTypeWithStdHandling(Class<?> rawType, JsonSerializer<
350351
public String toString() {
351352
return "(@JsonValue serializer for method " + _accessorMethod.getDeclaringClass() + "#" + _accessorMethod.getName() + ")";
352353
}
354+
355+
/*
356+
/**********************************************************
357+
/* Helper class
358+
/**********************************************************
359+
*/
360+
361+
/**
362+
* Silly little wrapper class we need to re-route type serialization so that we can
363+
* override Object to use for type id (logical type) even when asking serialization
364+
* of something else (delegate type)
365+
*/
366+
static class TypeSerializerRerouter
367+
extends TypeSerializer
368+
{
369+
protected final TypeSerializer _typeSerializer;
370+
protected final Object _forObject;
371+
372+
public TypeSerializerRerouter(TypeSerializer ts, Object ob) {
373+
_typeSerializer = ts;
374+
_forObject = ob;
375+
}
376+
377+
@Override
378+
public TypeSerializer forProperty(BeanProperty prop) { // should never get called
379+
throw new UnsupportedOperationException();
380+
}
381+
382+
@Override
383+
public As getTypeInclusion() {
384+
return _typeSerializer.getTypeInclusion();
385+
}
386+
387+
@Override
388+
public String getPropertyName() {
389+
return _typeSerializer.getPropertyName();
390+
}
391+
392+
@Override
393+
public TypeIdResolver getTypeIdResolver() {
394+
return _typeSerializer.getTypeIdResolver();
395+
}
396+
397+
@Override
398+
public void writeTypePrefixForScalar(Object value, JsonGenerator gen) throws IOException {
399+
_typeSerializer.writeTypePrefixForScalar(_forObject, gen);
400+
}
401+
402+
@Override
403+
public void writeTypePrefixForObject(Object value, JsonGenerator gen) throws IOException {
404+
_typeSerializer.writeTypePrefixForObject(_forObject, gen);
405+
}
406+
407+
@Override
408+
public void writeTypePrefixForArray(Object value, JsonGenerator gen) throws IOException {
409+
_typeSerializer.writeTypePrefixForArray(_forObject, gen);
410+
}
411+
412+
@Override
413+
public void writeTypeSuffixForScalar(Object value, JsonGenerator gen) throws IOException {
414+
_typeSerializer.writeTypeSuffixForScalar(_forObject, gen);
415+
}
416+
417+
@Override
418+
public void writeTypeSuffixForObject(Object value, JsonGenerator gen) throws IOException {
419+
_typeSerializer.writeTypeSuffixForObject(_forObject, gen);
420+
}
421+
422+
@Override
423+
public void writeTypeSuffixForArray(Object value, JsonGenerator gen) throws IOException {
424+
_typeSerializer.writeTypeSuffixForArray(_forObject, gen);
425+
}
426+
427+
public void writeTypePrefixForScalar(Object value, JsonGenerator gen, Class<?> type) throws IOException {
428+
_typeSerializer.writeTypePrefixForScalar(_forObject, gen, type);
429+
}
430+
431+
public void writeTypePrefixForObject(Object value, JsonGenerator gen, Class<?> type) throws IOException {
432+
_typeSerializer.writeTypePrefixForObject(_forObject, gen, type);
433+
}
434+
435+
public void writeTypePrefixForArray(Object value, JsonGenerator gen, Class<?> type) throws IOException {
436+
_typeSerializer.writeTypePrefixForArray(_forObject, gen, type);
437+
}
438+
439+
@Override
440+
public void writeCustomTypePrefixForScalar(Object value, JsonGenerator gen, String typeId)
441+
throws IOException {
442+
_typeSerializer.writeCustomTypePrefixForScalar(_forObject, gen, typeId);
443+
}
444+
445+
@Override
446+
public void writeCustomTypePrefixForObject(Object value, JsonGenerator gen, String typeId) throws IOException {
447+
_typeSerializer.writeCustomTypePrefixForObject(_forObject, gen, typeId);
448+
}
449+
450+
@Override
451+
public void writeCustomTypePrefixForArray(Object value, JsonGenerator gen, String typeId) throws IOException {
452+
_typeSerializer.writeCustomTypePrefixForArray(_forObject, gen, typeId);
453+
}
454+
455+
@Override
456+
public void writeCustomTypeSuffixForScalar(Object value, JsonGenerator gen, String typeId) throws IOException {
457+
_typeSerializer.writeCustomTypeSuffixForScalar(_forObject, gen, typeId);
458+
}
459+
460+
@Override
461+
public void writeCustomTypeSuffixForObject(Object value, JsonGenerator gen, String typeId) throws IOException {
462+
_typeSerializer.writeCustomTypeSuffixForObject(_forObject, gen, typeId);
463+
}
464+
465+
@Override
466+
public void writeCustomTypeSuffixForArray(Object value, JsonGenerator gen, String typeId) throws IOException {
467+
_typeSerializer.writeCustomTypeSuffixForArray(_forObject, gen, typeId);
468+
}
469+
}
353470
}

src/test/java/com/fasterxml/jackson/databind/jsontype/TestDefaultWithCreators.java

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.fasterxml.jackson.databind.jsontype;
22

3+
import org.junit.Assert;
4+
35
import com.fasterxml.jackson.annotation.*;
46

57
import com.fasterxml.jackson.databind.BaseMapTest;
@@ -8,12 +10,6 @@
810
public class TestDefaultWithCreators
911
extends BaseMapTest
1012
{
11-
/*
12-
/**********************************************************
13-
/* Helper types
14-
/**********************************************************
15-
*/
16-
1713
static abstract class Job
1814
{
1915
public long id;
@@ -36,7 +32,31 @@ public UrlJob(@JsonProperty("id") long id, @JsonProperty("url") String url,
3632
public String getUrl() { return url; }
3733
public int getCount() { return count; }
3834
}
35+
36+
// [databind#1385]
37+
static class Bean1385Wrapper
38+
{
39+
public Object value;
40+
41+
protected Bean1385Wrapper() { }
42+
public Bean1385Wrapper(Object v) { value = v; }
43+
}
44+
45+
static class Bean1385
46+
{
47+
private byte[] raw;
3948

49+
@JsonCreator(mode=JsonCreator.Mode.DELEGATING)
50+
public Bean1385(byte[] raw) {
51+
this.raw = raw.clone();
52+
}
53+
54+
@JsonValue
55+
public byte[] getBytes() {
56+
return raw;
57+
}
58+
}
59+
4060
/*
4161
/**********************************************************
4262
/* Unit tests
@@ -58,4 +78,21 @@ public void testWithCreators() throws Exception
5878
assertEquals("http://foo", o2.getUrl());
5979
assertEquals(3, o2.getCount());
6080
}
61-
}
81+
82+
// [databind#1385]
83+
public void testWithCreatorAndJsonValue() throws Exception
84+
{
85+
final byte[] BYTES = new byte[] { 1, 2, 3, 4, 5 };
86+
ObjectMapper mapper = new ObjectMapper();
87+
mapper.enableDefaultTyping();
88+
String json = mapper.writeValueAsString(new Bean1385Wrapper(
89+
new Bean1385(BYTES)
90+
));
91+
Bean1385Wrapper result = mapper.readValue(json, Bean1385Wrapper.class);
92+
assertNotNull(result);
93+
assertNotNull(result.value);
94+
assertEquals(Bean1385.class, result.value.getClass());
95+
Bean1385 b = (Bean1385) result.value;
96+
Assert.assertArrayEquals(BYTES, b.raw);
97+
}
98+
}

src/test/java/com/fasterxml/jackson/databind/jsontype/ext/ExternalTypeIdTest.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -473,22 +473,14 @@ public void testWithAsValue() throws Exception
473473
ExternalTypeWithNonPOJO input = new ExternalTypeWithNonPOJO(new AsValueThingy(12345L));
474474
String json = MAPPER.writeValueAsString(input);
475475
assertNotNull(json);
476-
assertEquals("{\"value\":12345,\"type\":\"date\"}", json);
476+
assertEquals("{\"value\":12345,\"type\":\"thingy\"}", json);
477477

478478
// and get it back too:
479479
ExternalTypeWithNonPOJO result = MAPPER.readValue(json, ExternalTypeWithNonPOJO.class);
480480
assertNotNull(result);
481481
assertNotNull(result.value);
482-
/* 13-Feb-2013, tatu: Urgh. I don't think this can work quite as intended...
483-
* since POJO type, and type of thing @JsonValue annotated method returns
484-
* are not related. Best we can do is thus this:
485-
*/
486-
/*
487482
assertEquals(AsValueThingy.class, result.value.getClass());
488483
assertEquals(12345L, ((AsValueThingy) result.value).rawDate);
489-
*/
490-
assertEquals(Date.class, result.value.getClass());
491-
assertEquals(12345L, ((Date) result.value).getTime());
492484
}
493485

494486
// for [databind#222]

src/test/java/com/fasterxml/jackson/databind/ser/TestJsonValue.java

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,11 @@ static class External {
120120
External(Internal e) { i = e.value; }
121121
}
122122

123-
// [Issue#167]
123+
// [databind#167]
124124

125125
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "boingo")
126-
@JsonSubTypes(value = {@JsonSubTypes.Type(name = "boopsy", value = AdditionInterfaceImpl.class) })
126+
@JsonSubTypes(value = {@JsonSubTypes.Type(name = "boopsy", value = AdditionInterfaceImpl.class)
127+
})
127128
static interface AdditionInterface
128129
{
129130
public int add(int in);
@@ -148,25 +149,6 @@ public int add(int in) {
148149
return in + toAdd;
149150
}
150151
}
151-
152-
public static class NegatingAdditionInterface implements AdditionInterface
153-
{
154-
final AdditionInterface delegate;
155-
156-
public NegatingAdditionInterface(AdditionInterface delegate) {
157-
this.delegate = delegate;
158-
}
159-
160-
@Override
161-
public int add(int in) {
162-
return delegate.add(-in);
163-
}
164-
165-
@JsonValue
166-
public AdditionInterface getDelegate() {
167-
return delegate;
168-
}
169-
}
170152

171153
static class Bean838 {
172154
@JsonValue
@@ -258,13 +240,6 @@ public void testPolymorphicSerdeWithDelegate() throws Exception
258240
String json = MAPPER.writeValueAsString(adder);
259241
assertEquals("{\"boingo\":\"boopsy\",\"toAdd\":1}", json);
260242
assertEquals(2, MAPPER.readValue(json, AdditionInterface.class).add(1));
261-
262-
adder = new NegatingAdditionInterface(adder);
263-
assertEquals(0, adder.add(1));
264-
json = MAPPER.writeValueAsString(adder);
265-
266-
assertEquals("{\"boingo\":\"boopsy\",\"toAdd\":1}", json);
267-
assertEquals(2, MAPPER.readValue(json, AdditionInterface.class).add(1));
268243
}
269244

270245
public void testJsonValueWithCustomOverride() throws Exception

0 commit comments

Comments
 (0)