Skip to content

Commit 0d17b85

Browse files
committed
#1330 Support ability to configure global defaultTyping
1 parent 6c35dc3 commit 0d17b85

File tree

3 files changed

+294
-0
lines changed

3 files changed

+294
-0
lines changed

runtime/src/main/java/io/micronaut/jackson/JacksonConfiguration.java

+17
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public class JacksonConfiguration {
6565
private Map<JsonParser.Feature, Boolean> parser = Collections.emptyMap();
6666
private Map<JsonGenerator.Feature, Boolean> generator = Collections.emptyMap();
6767
private JsonInclude.Include serializationInclusion = JsonInclude.Include.NON_EMPTY;
68+
private ObjectMapper.DefaultTyping defaultTyping = null;
6869
private PropertyNamingStrategy propertyNamingStrategy = null;
6970

7071
/**
@@ -110,6 +111,13 @@ public JsonInclude.Include getSerializationInclusion() {
110111
return serializationInclusion;
111112
}
112113

114+
/**
115+
* @return The global defaultTyping using for Polymorphic handling
116+
*/
117+
public ObjectMapper.DefaultTyping getDefaultTyping() {
118+
return defaultTyping;
119+
}
120+
113121
/**
114122
* @return The default locale to use
115123
*/
@@ -273,6 +281,15 @@ public void setSerializationInclusion(JsonInclude.Include serializationInclusion
273281
}
274282
}
275283

284+
/**
285+
* Sets the global defaultTyping using for Polymorphic handling.
286+
*
287+
* @param defaultTyping The defaultTyping
288+
*/
289+
public void setDefaultTyping(ObjectMapper.DefaultTyping defaultTyping) {
290+
this.defaultTyping = defaultTyping;
291+
}
292+
276293
/**
277294
* Sets the property naming strategy.
278295
*

runtime/src/main/java/io/micronaut/jackson/ObjectMapperFactory.java

+5
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ public ObjectMapper objectMapper(@Nullable JacksonConfiguration jacksonConfigura
135135

136136
if (hasConfiguration) {
137137

138+
ObjectMapper.DefaultTyping defaultTyping = jacksonConfiguration.getDefaultTyping();
139+
if (defaultTyping != null) {
140+
objectMapper.enableDefaultTyping(defaultTyping);
141+
}
142+
138143
JsonInclude.Include include = jacksonConfiguration.getSerializationInclusion();
139144
if (include != null) {
140145
objectMapper.setSerializationInclusion(include);

runtime/src/test/groovy/io/micronaut/jackson/parser/JacksonProcessorSpec.groovy

+272
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@
1515
*/
1616
package io.micronaut.jackson.parser
1717

18+
import com.fasterxml.jackson.annotation.JsonTypeInfo
1819
import com.fasterxml.jackson.core.JsonParseException
1920
import com.fasterxml.jackson.core.io.JsonEOFException
2021
import com.fasterxml.jackson.databind.JsonNode
2122
import com.fasterxml.jackson.databind.ObjectMapper
2223
import com.fasterxml.jackson.databind.node.ArrayNode
24+
import groovy.transform.ToString
2325
import io.micronaut.context.ApplicationContext
2426
import io.micronaut.context.DefaultApplicationContext
27+
import io.micronaut.context.env.PropertySource
2528
import org.reactivestreams.Subscriber
2629
import org.reactivestreams.Subscription
2730
import spock.lang.AutoCleanup
@@ -103,6 +106,219 @@ class JacksonProcessorSpec extends Specification {
103106
foo.age == 10
104107
}
105108

109+
void "test Jackson polymorphic deserialization"() {
110+
given:
111+
ObjectMapper objectMapper = applicationContext.getBean(ObjectMapper)
112+
JacksonProcessor processor = new JacksonProcessor()
113+
Bicycle bicycle = new MountainBike(3, 30, 10)
114+
Garage instance = new Garage(bicycle: bicycle)
115+
116+
when:
117+
def string = objectMapper.writeValueAsString(instance)
118+
byte[] bytes = objectMapper.writeValueAsBytes(instance)
119+
boolean complete = false
120+
JsonNode node = null
121+
Throwable error = null
122+
int nodeCount = 0
123+
processor.subscribe(new Subscriber<JsonNode>() {
124+
@Override
125+
void onSubscribe(Subscription s) {
126+
s.request(Long.MAX_VALUE)
127+
}
128+
129+
@Override
130+
void onNext(JsonNode jsonNode) {
131+
nodeCount++
132+
node = jsonNode
133+
}
134+
135+
@Override
136+
void onError(Throwable t) {
137+
error = t
138+
}
139+
140+
@Override
141+
void onComplete() {
142+
complete = true
143+
}
144+
})
145+
processor.onSubscribe(new Subscription() {
146+
@Override
147+
void request(long n) {
148+
149+
}
150+
151+
@Override
152+
void cancel() {
153+
154+
}
155+
})
156+
processor.onNext(bytes)
157+
processor.onComplete()
158+
159+
then:
160+
complete
161+
node != null
162+
error == null
163+
nodeCount == 1
164+
string == '{"bicycle":{"gear":3,"speed":30,"seatHeight":10}}'
165+
166+
when:
167+
Garage garage = objectMapper.treeToValue(node, Garage)
168+
169+
then:
170+
!(garage.bicycle instanceof MountainBike)
171+
}
172+
173+
void "test Jackson polymorphic deserialization, per class"() {
174+
given:
175+
ObjectMapper objectMapper = applicationContext.getBean(ObjectMapper)
176+
JacksonProcessor processor = new JacksonProcessor()
177+
Dog dog = new Dog(name: "Daisy", barkVolume: 1111.1D)
178+
Zoo instance = new Zoo(animal: dog)
179+
180+
when:
181+
def string = objectMapper.writeValueAsString(instance)
182+
byte[] bytes = objectMapper.writeValueAsBytes(instance)
183+
boolean complete = false
184+
JsonNode node = null
185+
Throwable error = null
186+
int nodeCount = 0
187+
processor.subscribe(new Subscriber<JsonNode>() {
188+
@Override
189+
void onSubscribe(Subscription s) {
190+
s.request(Long.MAX_VALUE)
191+
}
192+
193+
@Override
194+
void onNext(JsonNode jsonNode) {
195+
nodeCount++
196+
node = jsonNode
197+
}
198+
199+
@Override
200+
void onError(Throwable t) {
201+
error = t
202+
}
203+
204+
@Override
205+
void onComplete() {
206+
complete = true
207+
}
208+
})
209+
processor.onSubscribe(new Subscription() {
210+
@Override
211+
void request(long n) {
212+
213+
}
214+
215+
@Override
216+
void cancel() {
217+
218+
}
219+
})
220+
processor.onNext(bytes)
221+
processor.onComplete()
222+
223+
then:
224+
complete
225+
node != null
226+
error == null
227+
nodeCount == 1
228+
string == '{"animal":{"@class":"io.micronaut.jackson.parser.Dog","name":"Daisy","barkVolume":1111.1}}'
229+
230+
when:
231+
Zoo zoo = objectMapper.treeToValue(node, Zoo)
232+
233+
then:
234+
zoo.animal instanceof Dog
235+
236+
when:
237+
Dog animal = (Dog) zoo.animal
238+
239+
then:
240+
animal != null
241+
animal.name == "Daisy"
242+
animal.barkVolume == 1111.1D
243+
}
244+
245+
void "test Jackson polymorphic deserialization, global default typing"() {
246+
given:
247+
ApplicationContext applicationContext1 = new DefaultApplicationContext("test")
248+
applicationContext1.environment.addPropertySource(PropertySource.of(
249+
'test', ['jackson.defaultTyping':'NON_FINAL']
250+
))
251+
applicationContext1 = applicationContext1.start()
252+
ObjectMapper objectMapper = applicationContext1.getBean(ObjectMapper)
253+
JacksonProcessor processor = new JacksonProcessor()
254+
Bicycle bicycle = new MountainBike(3, 30, 10)
255+
Garage instance = new Garage(bicycle: bicycle)
256+
257+
when:
258+
def string = objectMapper.writeValueAsString(instance)
259+
byte[] bytes = objectMapper.writeValueAsBytes(instance)
260+
boolean complete = false
261+
JsonNode node = null
262+
Throwable error = null
263+
int nodeCount = 0
264+
processor.subscribe(new Subscriber<JsonNode>() {
265+
@Override
266+
void onSubscribe(Subscription s) {
267+
s.request(Long.MAX_VALUE)
268+
}
269+
270+
@Override
271+
void onNext(JsonNode jsonNode) {
272+
nodeCount++
273+
node = jsonNode
274+
}
275+
276+
@Override
277+
void onError(Throwable t) {
278+
error = t
279+
}
280+
281+
@Override
282+
void onComplete() {
283+
complete = true
284+
}
285+
})
286+
processor.onSubscribe(new Subscription() {
287+
@Override
288+
void request(long n) {
289+
290+
}
291+
292+
@Override
293+
void cancel() {
294+
295+
}
296+
})
297+
processor.onNext(bytes)
298+
processor.onComplete()
299+
300+
then:
301+
complete
302+
node != null
303+
error == null
304+
nodeCount == 1
305+
string == '["io.micronaut.jackson.parser.Garage",{"bicycle":["io.micronaut.jackson.parser.MountainBike",{"gear":3,"speed":30,"seatHeight":10}]}]'
306+
307+
when:
308+
Garage garage = objectMapper.treeToValue(node, Garage)
309+
310+
then:
311+
garage.bicycle instanceof MountainBike
312+
313+
when:
314+
MountainBike mountainBike = (MountainBike) garage.bicycle
315+
316+
then:
317+
mountainBike != null
318+
mountainBike.gear == 3
319+
mountainBike.speed == 30
320+
mountainBike.seatHeight == 10
321+
}
106322

107323
void "test publish JSON array async"() {
108324

@@ -289,3 +505,59 @@ class Foo {
289505
String name
290506
Integer age
291507
}
508+
509+
@ToString
510+
class Garage {
511+
String name
512+
Bicycle bicycle
513+
}
514+
515+
@ToString
516+
class Bicycle {
517+
int gear
518+
int speed
519+
520+
Bicycle() {}
521+
522+
Bicycle(int gear, int speed) {
523+
this.gear = gear
524+
this.speed = speed
525+
}
526+
527+
void applyBrake(int decrement) {
528+
speed -= decrement
529+
}
530+
531+
void speedUp(int increment) {
532+
speed += increment
533+
}
534+
}
535+
536+
@ToString
537+
class MountainBike extends Bicycle {
538+
int seatHeight
539+
540+
MountainBike() {}
541+
542+
MountainBike(int gear,int speed, int seatHeight) {
543+
super(gear, speed)
544+
this.seatHeight = seatHeight
545+
}
546+
}
547+
548+
@ToString
549+
class Zoo {
550+
Animal animal
551+
}
552+
553+
@ToString
554+
@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
555+
class Animal {
556+
String name
557+
}
558+
559+
@ToString
560+
class Dog extends Animal {
561+
double barkVolume
562+
563+
}

0 commit comments

Comments
 (0)