Skip to content

Commit 204fee3

Browse files
dhennekePhil404
authored andcommitted
Fix serialization of beans that are annotated with @JsonTypeInfo() (#26)
* Add test cases for polymorphic classes We have currently a problem with polymorphic classes. They could not created by HALMapper in the correct HAL structure. Except the polymorphic class is in a other class embedded. * Add test cases for polymorphic classes without List and other Annotation (as.property) * Fix serialization of beans that are annotated with @JsonTypeInfo() Co-authored-by: Philipp Westphalen <[email protected]>
1 parent 2c8e9fb commit 204fee3

File tree

3 files changed

+439
-6
lines changed

3 files changed

+439
-6
lines changed

src/main/java/io/openapitools/jackson/dataformat/hal/ser/HALBeanSerializer.java

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package io.openapitools.jackson.dataformat.hal.ser;
22

33
import com.fasterxml.jackson.core.JsonGenerator;
4+
import com.fasterxml.jackson.core.JsonToken;
5+
import com.fasterxml.jackson.core.type.WritableTypeId;
46
import com.fasterxml.jackson.databind.BeanDescription;
57
import com.fasterxml.jackson.databind.SerializerProvider;
8+
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
69
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
710
import com.fasterxml.jackson.databind.ser.impl.ObjectIdWriter;
811
import com.fasterxml.jackson.databind.ser.std.BeanSerializerBase;
12+
import com.fasterxml.jackson.databind.util.NameTransformer;
913
import io.openapitools.jackson.dataformat.hal.HALLink;
1014
import io.openapitools.jackson.dataformat.hal.annotation.Curie;
1115
import io.openapitools.jackson.dataformat.hal.annotation.Curies;
@@ -63,7 +67,23 @@ protected BeanSerializerBase withIgnorals(Set<String> set) {
6367
@Override
6468
public void serialize(Object bean, JsonGenerator jgen, SerializerProvider provider) throws IOException {
6569
FilteredProperties filtered = new FilteredProperties(bean, provider, beanDescription);
70+
71+
// this serializer acts autonomous and creates the complete object
72+
jgen.writeStartObject();
73+
filtered.serialize(bean, jgen, provider);
74+
jgen.writeEndObject();
75+
}
76+
77+
@Override
78+
public void serializeWithType(Object bean, JsonGenerator jgen, SerializerProvider provider,
79+
TypeSerializer typeSer) throws IOException {
80+
FilteredProperties filtered = new FilteredProperties(bean, provider, beanDescription);
81+
82+
// this serializer lets the TypeSerializer create the outer frame and inserts the rest of the object
83+
WritableTypeId typeIdDef = _typeIdDef(typeSer, bean, JsonToken.START_OBJECT);
84+
typeSer.writeTypePrefix(jgen, typeIdDef);
6685
filtered.serialize(bean, jgen, provider);
86+
typeSer.writeTypeSuffix(jgen, typeIdDef);
6787
}
6888

6989
/**
@@ -160,8 +180,6 @@ private void populateCurieMap(BeanDescription beanDescription) {
160180
}
161181

162182
public void serialize(Object bean, JsonGenerator jgen, SerializerProvider provider) throws IOException {
163-
jgen.writeStartObject();
164-
165183
if (!links.isEmpty()) {
166184
jgen.writeFieldName("_links");
167185
jgen.writeStartObject();
@@ -177,8 +195,11 @@ public void serialize(Object bean, JsonGenerator jgen, SerializerProvider provid
177195
jgen.writeStartObject();
178196
for (String rel : embedded.keySet()) {
179197
try {
180-
jgen.writeFieldName(rel);
181-
jgen.writeObject(embedded.get(rel).get(bean));
198+
// use the relation as new name for the original property
199+
BeanPropertyWriter prop = renameBeanProperty(embedded.get(rel), rel);
200+
201+
// serialize the field as normal field
202+
prop.serializeAsField(bean, jgen, provider);
182203
} catch (Exception e) {
183204
wrapAndThrow(provider, e, bean, rel);
184205
}
@@ -193,8 +214,6 @@ public void serialize(Object bean, JsonGenerator jgen, SerializerProvider provid
193214
wrapAndThrow(provider, e, bean, prop.getName());
194215
}
195216
}
196-
197-
jgen.writeEndObject();
198217
}
199218

200219
private void addEmbeddedProperty(String rel, BeanPropertyWriter property) {
@@ -219,8 +238,22 @@ private String applyCurieToRel(String rel, String curie) {
219238
return (null == curie) ? rel : curie + ":" + rel;
220239
}
221240

241+
private BeanPropertyWriter renameBeanProperty(BeanPropertyWriter prop, String newName) {
242+
return prop.rename(new NameTransformer() {
243+
@Override
244+
public String transform(String name) {
245+
return newName;
246+
}
247+
248+
@Override
249+
public String reverse(String transformed) {
250+
return null;
251+
}
252+
});
253+
}
222254
}
223255

256+
224257
/**
225258
* Representing either a single link (one-to-one relation) or a collection of links.
226259
*/
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package io.openapitoools.jackson.dataformat.hal.ser;
2+
3+
import static org.junit.Assert.assertEquals;
4+
5+
import com.fasterxml.jackson.annotation.JsonSubTypes;
6+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
7+
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
8+
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
9+
import com.fasterxml.jackson.databind.ObjectMapper;
10+
import io.openapitools.jackson.dataformat.hal.HALLink;
11+
import io.openapitools.jackson.dataformat.hal.HALMapper;
12+
import io.openapitools.jackson.dataformat.hal.annotation.EmbeddedResource;
13+
import io.openapitools.jackson.dataformat.hal.annotation.Link;
14+
import io.openapitools.jackson.dataformat.hal.annotation.Resource;
15+
import java.net.URI;
16+
import java.util.Arrays;
17+
import java.util.Collection;
18+
import java.util.List;
19+
import org.junit.Test;
20+
21+
public class HALBeanSerializerPolymorphicWithExistingFieldIT {
22+
23+
ObjectMapper om = new HALMapper();
24+
25+
@Test
26+
public void testSerializationForResourceWithEmbeddableList() throws Exception {
27+
@Resource
28+
class TopResource {
29+
30+
public String id = "1";
31+
32+
@Link public HALLink self = new HALLink.Builder(URI.create("/top/1")).build();
33+
34+
@Link("child")
35+
public List<HALLink> childResourcesLink =
36+
Arrays.asList(
37+
new HALLink.Builder(URI.create("/top/1/child/1")).build(),
38+
new HALLink.Builder(URI.create("/top/1/child/2")).build());
39+
40+
@EmbeddedResource("child")
41+
public Collection<ChildResource> children =
42+
Arrays.asList(new ChildResource("1"), new OtherChildResource("2", "Max"));
43+
}
44+
45+
TopResource resource = new TopResource();
46+
String json = om.writeValueAsString(resource);
47+
assertEquals(
48+
"{"
49+
+ "\"_links\":{"
50+
+ "\"child\":[{\"href\":\"/top/1/child/1\"},{\"href\":\"/top/1/child/2\"}],"
51+
+ "\"self\":{\"href\":\"/top/1\"}"
52+
+ "},"
53+
+ "\"_embedded\":{"
54+
+ "\"child\":["
55+
+ "{\"_links\":{\"self\":{\"href\":\"/top/1/child/1\"}},\"type\":\"ChildResource\",\"id\":\"1\"},"
56+
+ "{\"_links\":{\"self\":{\"href\":\"/top/1/child/2\"}},\"type\":\"OtherChildResource\",\"id\":\"2\",\"name\":\"Max\"}]"
57+
+ "},"
58+
+ "\"id\":\"1\"}",
59+
json);
60+
}
61+
62+
@Test
63+
public void testSerializationForResourceWithList() throws Exception {
64+
@Resource
65+
class TopResourceWithoutEmbedded {
66+
public String id = "1";
67+
68+
@Link public HALLink self = new HALLink.Builder(URI.create("/top/1")).build();
69+
70+
public Collection<ChildResource> children =
71+
Arrays.asList(new ChildResource("1"), new OtherChildResource("2", "Max"));
72+
}
73+
74+
TopResourceWithoutEmbedded resource = new TopResourceWithoutEmbedded();
75+
String json = om.writeValueAsString(resource);
76+
assertEquals(
77+
"{"
78+
+ "\"_links\":{"
79+
+ "\"self\":{\"href\":\"/top/1\"}"
80+
+ "},"
81+
+ "\"id\":\"1\","
82+
+ "\"children\":["
83+
+ "{\"_links\":{\"self\":{\"href\":\"/top/1/child/1\"}},\"type\":\"ChildResource\",\"id\":\"1\"},"
84+
+ "{\"_links\":{\"self\":{\"href\":\"/top/1/child/2\"}},\"type\":\"OtherChildResource\",\"id\":\"2\",\"name\":\"Max\"}]"
85+
+ "}",
86+
json);
87+
}
88+
89+
@Test
90+
public void testSerializationForResourceEmbedded() throws Exception {
91+
@Resource
92+
class SimpleTopResourceEmbedded {
93+
public String id = "1";
94+
95+
@Link public HALLink self = new HALLink.Builder(URI.create("/top/1")).build();
96+
97+
@EmbeddedResource public ChildResource child = new ChildResource("1");
98+
}
99+
100+
SimpleTopResourceEmbedded resource = new SimpleTopResourceEmbedded();
101+
String json = om.writeValueAsString(resource);
102+
assertEquals(
103+
"{"
104+
+ "\"_links\":{"
105+
+ "\"self\":{\"href\":\"/top/1\"}"
106+
+ "},"
107+
+ "\"_embedded\":{"
108+
+ "\"child\":{\"_links\":{\"self\":{\"href\":\"/top/1/child/1\"}},\"type\":\"ChildResource\",\"id\":\"1\"}"
109+
+ "},"
110+
+ "\"id\":\"1\"}",
111+
json);
112+
}
113+
114+
@Test
115+
public void testSerializationForResource() throws Exception {
116+
@Resource
117+
class SimpleTopResource {
118+
public String id = "1";
119+
120+
@Link public HALLink self = new HALLink.Builder(URI.create("/top/1")).build();
121+
122+
public ChildResource child = new ChildResource("1");
123+
}
124+
125+
SimpleTopResource resource = new SimpleTopResource();
126+
String json = om.writeValueAsString(resource);
127+
assertEquals(
128+
"{"
129+
+ "\"_links\":{"
130+
+ "\"self\":{\"href\":\"/top/1\"}"
131+
+ "},"
132+
+ "\"id\":\"1\","
133+
+ "\"child\":{"
134+
+ "\"_links\":{\"self\":{\"href\":\"/top/1/child/1\"}},\"type\":\"ChildResource\",\"id\":\"1\""
135+
+ "}}",
136+
json);
137+
}
138+
139+
@Test
140+
public void testSerializationForAnnotatedPolymorphicObject() throws Exception {
141+
ChildResource resource = new ChildResource("1");
142+
String json = om.writeValueAsString(resource);
143+
assertEquals(
144+
"{"
145+
+ "\"_links\":{"
146+
+ "\"self\":{\"href\":\"/top/1/child/1\"}"
147+
+ "},"
148+
+ "\"type\":\"ChildResource\","
149+
+ "\"id\":\"1\"}",
150+
json);
151+
}
152+
153+
@Test
154+
public void testSerializationForNotAnnotatedPolymorphicObject() throws Exception {
155+
ChildResource resource = new OtherChildResource("1", "Max");
156+
String json = om.writeValueAsString(resource);
157+
assertEquals(
158+
"{"
159+
+ "\"_links\":{"
160+
+ "\"self\":{\"href\":\"/top/1/child/1\"}"
161+
+ "},"
162+
+ "\"type\":\"OtherChildResource\","
163+
+ "\"id\":\"1\","
164+
+ "\"name\":\"Max\"}",
165+
json);
166+
}
167+
168+
@JsonTypeInfo(
169+
use = Id.NAME,
170+
include = As.EXISTING_PROPERTY,
171+
property = "type",
172+
defaultImpl = ChildResource.class)
173+
@JsonSubTypes({@JsonSubTypes.Type(value = OtherChildResource.class, name = "OtherChildResource")})
174+
@Resource
175+
public static class ChildResource {
176+
177+
public String type = "ChildResource";
178+
179+
public String id;
180+
181+
@Link public HALLink self;
182+
183+
public ChildResource(String id) {
184+
this.id = id;
185+
self = new HALLink.Builder(URI.create("/top/1/child/" + id)).build();
186+
}
187+
}
188+
189+
@Resource
190+
public static class OtherChildResource extends ChildResource {
191+
192+
public String type = "OtherChildResource";
193+
194+
public String name;
195+
196+
public OtherChildResource(String id, String name) {
197+
super(id);
198+
this.name = name;
199+
}
200+
}
201+
}

0 commit comments

Comments
 (0)