Closed ChristianSch closed 5 years ago
You should be able to control this by overriding acceptJsonFormatVisitor
in your custom serializer. An example is https://github.com/ScaCap/spring-auto-restdocs/blob/master/spring-auto-restdocs-core/src/test/java/capital/scalable/restdocs/jackson/BigDecimalSerializer.java
The entry point for walking through the POJOs with Jackson is
https://github.com/ScaCap/spring-auto-restdocs/blob/master/spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/jackson/FieldDocumentationGenerator.java#L79
So ObjectMapper.acceptJsonFormatVisitor
is used. Its documentation states
Method for visiting type hierarchy for given type, using specified visitor. Visitation uses
Serializer
hierarchy and related properties
It only sees the serialization side but this is fine in this case. Please make sure that the provided ObjectMapper
needs to have the custom serializer configured (done via .alwaysDo(JacksonResultHandlers.prepareJackson(objectMapper))
). The different types (object, array, string, ...) are handled in https://github.com/ScaCap/spring-auto-restdocs/blob/master/spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/jackson/FieldDocumentationVisitorWrapper.java
Thanks so much! I understood you as follows. I provided the serializer with an implementation of acceptJsonFormatVisitor
reflecting the field of a list of strings from the object.
public class IObjectWithIdListSerializer extends JsonSerializer<List<IObjectWithId>> {
@Override
public void serialize(List<IObjectWithId> objects, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartArray();
for (IObjectWithId o : objects) {
jsonGenerator.writeObject(o.getId().toString());
}
jsonGenerator.writeEndArray();
}
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
throws JsonMappingException {
if (visitor != null) {
visitor.expectArrayFormat(typeHint).itemsFormat(JsonFormatTypes.STRING);
}
}
}
The objectMapper
was already configured.
@Slf4j
@Transactional
@ActiveProfiles("test")
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {TestApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public abstract class AbstractMvcTest {
// ...
@Autowired
protected ObjectMapper objectMapper;
@Before
public void setUp() {
// ...
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.addFilters(springSecurityFilterChain)
.alwaysDo(JacksonResultHandlers.prepareJackson(objectMapper))
.alwaysDo(commonDocumentation())
.apply(MockMvcRestDocumentation.documentationConfiguration(restDocumentation)
.uris()
.withScheme("http")
.withHost("localhost")
.withPort(8080)
.and().snippets()
.withDefaults(CliDocumentation.curlRequest(),
HttpDocumentation.httpRequest(),
HttpDocumentation.httpResponse(),
AutoDocumentation.requestFields(),
AutoDocumentation.responseFields(),
AutoDocumentation.pathParameters(),
AutoDocumentation.requestParameters(),
AutoDocumentation.description(),
AutoDocumentation.methodAndPath(),
AutoDocumentation.section()))
.build();
// ...
}
}
However, it did not change the output. Did I misunderstand you or have I missed anything? Thanks.
I played around with your serializer in the Spring WebMVC example project and it looks like serializers that are added by annotations are not considered (correctly) by Spring Auto REST Docs, e.g.
@JsonSerialize(using = IObjectWithIdListSerializer.class)
I tried adding the serializer on the module
JavaType type = mapper.getTypeFactory().
constructCollectionType(List.class, IObjectWithId.class);
module.addSerializer((Class<List<IObjectWithId>>) type.getRawClass(),
new IObjectWithIdListSerializer());
but then it was applied to other classes as well and of course caused issues.
Thus, I can only suggest a workaround:
Create a IObjectWithIdSerializer
public class IObjectWithIdSerializer extends StdSerializer<IObjectWithId> {
public IObjectWithIdSerializer() {
super(IObjectWithId.class);
}
@Override
public void serialize(IObjectWithId object, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeObject(object.getId().toString());;
}
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
throws JsonMappingException {
if (visitor != null) {
visitor.expectStringFormat(typeHint);
}
}
}
and register it
module.addSerializer(IObjectWithId.class, new IObjectWithIdSerializer());
The result is
|processedUserIds
|Array[Object]
|true
|Some description that explains that it is an array of IDs.
and thus there are no nested fields or any recursion.
Note: To avoid recursive expansion @RestdocsNotExpanded
can be used.
Thank you for your time. This doesn't work for me as I need the models sometimes as objects, not only as their id's. With @RestdocsNotExpanded
I have no problems besides the wrong type for the arrays. This works for me though. Thanks.
Glad that you got it working. It's of course not perfect but there is also no easy solution in SARD.
Hi there,
Thanks for your work!
So my problem is the following: I have a list of objects in several returns, which I serialize with a custom JsonSerializer to a list of their ids. Eg:
The json response actually contains the properly serialized list of ids, but SARD does not detect the type correctly and has a kind of infinite recursion going on with the actual object.
How would I let SARD know the type, how would I enforce it? If it's not capable of that I'd be glad of any pointers to contribute to this. Cheers.
BTW, this is one row of the table in
auto-response-fields.adoc
, which might indicate erroneous behaviour when it comes to optionality in this case?