joffrey-bion / livedoc

A not-so-annotation-based documentation generator for REST and websocket services
MIT License
5 stars 2 forks source link

Jsondoc examples does not use settings from springs ObjectMapper #42

Closed ST-DDT closed 7 years ago

ST-DDT commented 7 years ago

The issue

The example generated by jsondoc does match the settings that have been set on the Jackson2HttpMessageConverter.

objectMapper.findAndRegisterModules()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
...

I don't know how the template is generated, so I cannot give you further hints how to fix this. If you give me a pointer where/how they are generated I might be able to present a solution.

Livedoc output

{
  "name": "",
  "startupTime": {
    "year": 0,
    "month": "JANUARY",
    "dayOfMonth": 0,
    "dayOfWeek": "MONDAY",
    "dayOfYear": 0,
    "monthValue": 0,
    "nano": 0,
    "hour": 0,
    "minute": 0,
    "second": 0,
    "chronology": {
      "calendarType": "",
      "id": ""
    }
  },
  "currentTime": {
    "year": 0,
    "month": "JANUARY",
    "dayOfMonth": 0,
    "dayOfWeek": "MONDAY",
    "dayOfYear": 0,
    "monthValue": 0,
    "nano": 0,
    "hour": 0,
    "minute": 0,
    "second": 0,
    "chronology": {
      "calendarType": "",
      "id": ""
    }
  }
}

Expected output

{
  "name": "",
  "startupTime": "2017-03-01T13:37:00Z",
  "currentTime": "2017-03-01T13:37:00Z"
}
joffrey-bion commented 7 years ago

Hi @ST-DDT , once again thanks for your feedback, it's always very constructive and useful.

There are basically 2 steps in template generation:

What I've changed so far resides in the Java objects creation. Jsondoc can only read Java fields to find properties, while Livedoc reads the properties returned by the configured PropertyScanner that is also used for the doc generation (which can be customized and could be anything). When using livedoc-springmvc and its SpringLivedocReaderFactory, the PropertyScanner is a JacksonPropertyScanner that I create using Spring's ObjectMapper, so it respects your Jackson annotations, and most of the configuration (@JsonIgnore, custom property names etc.).

This Java objects creation is done in the TemplateProvider. The serialization to a JSON string from these objects happens when serializing the big Livedoc object at the moment you request it from the controller, which means that these objects should be serialized using the Spring Jackson2HttpMessageConverter used for the /jsondoc endpoint. Which is why I'm surprised it's inconsistent with your settings.

I didn't change the serialization of the doc AFAIR, and the only thing that comes to my mind so far is the fact that I changed the type of the template field from ObjectTemplate (which extended LinkedHashMap) to a plain Java Object.

I'll look into this issue ASAP.

joffrey-bion commented 7 years ago

Hi @ST-DDT , I've got a couple questions to investigate this issue:

  1. How do you use livedoc-springmvc? Do you create your own controller?
  2. Do you create your own LivedocReader?
  3. Does your first sentence mean you are seeing your expected result with the original Jsondoc project but not with Livedoc?
  4. How/where/when do you configure the ObjectMapper used by Spring's Jackson2HttpMessageConverter?
ST-DDT commented 7 years ago
  1. How do you use livedoc-springmvc? Do you create your own controller?
@Bean
public JsonLivedocController documentationController() {
    String version = getCurrentVersion();
    String contextPath = getContextPath();
    List<String> packages = Arrays.asList("a", "b", "c");
    return new JsonLivedocController(version, contextPath, packages);
}

No other configuration for LiveDocs.

  1. Do you create your own LivedocReader?

I don't understand the question, but I guess I don't.

  1. Does your first sentence mean you are seeing your expected result with the original Jsondoc project but not with Livedoc?

No, I don't get the expected result with JsonDoc either. IIRC I get the following from JsonDoc:

{
    "currentTime": { },
    "name": "",
    "startupTime": { }
}
  1. How/where/when do you configure the ObjectMapper used by Spring's Jackson2HttpMessageConverter?
@Configration
public class MyWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        for (HttpMessageConverter<?> converter : converters) {
            if (converter instanceof AbstractJackson2HttpMessageConverter) {
                ((AbstractJackson2HttpMessageConverter) converter).getObjectMapper().disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            }
        }
    }

}
joffrey-bion commented 7 years ago

Thanks a lot for your answers.

Do you create your own LivedocReader?

I don't understand the question, but I guess I don't.

This was in fact answered by question 1. There is another JsonLivedocController constructor taking a custom LivedocReader object for the doc generation, but you used the usual one taking a package list. The constructor that you used creates a Spring-oriented LivedocReader, which is what we want here, so no issue on that side. FYI, in the next version of Livedoc, due to the fix of https://github.com/joffrey-bion/livedoc/issues/12, neither constructors will take the basePath as an argument, as it is now inferred by default from the current request context. You will still be able to force it via a setter of course.

Does your first sentence mean you are seeing your expected result with the original Jsondoc project but not with Livedoc?

No, I don't get the expected result with JsonDoc either.

Thanks for the clarification, I was really wondering how on earth this behaviour could have changed.

Regarding your ObjectMapper configuration, can you confirm this configuration is actually applied to your other endpoints? Does your API actually return dates in text format? I would just like to confirm whether this instanceof check is actually true at some point.

joffrey-bion commented 7 years ago

Ok, I guess I have found the issue.

In fact, Livedoc doesn't use the actual ObjectMapper instance used by Spring, but simply a Spring-generated ObjectMapper instance. Therefore, it cannot reflect any custom configuration.

I had written it this way temporarily, but I thought I had changed it to access the actual ObjectMapper bean. I guess I was wrong.

The culprit is the SpringLivedocReaderFactory:

public class SpringLivedocReaderFactory {

    public static LivedocReader getReader(List<String> packages) {
        Reflections reflections = LivedocUtils.newReflections(packages);
        AnnotatedTypesFinder annotatedTypesFinder = LivedocUtils.createAnnotatedTypesFinder(reflections);

        PropertyScanner propertyScanner = createJacksonPropertyScanner();
        DocReader springDocReader = new SpringDocReader(annotatedTypesFinder);
        DocReader baseDocReader = new LivedocAnnotationDocReader(annotatedTypesFinder);

        return new LivedocReaderBuilder().scanningPackages(packages)
                                         .withPropertyScanner(propertyScanner)
                                         .addDocReader(springDocReader)
                                         .addDocReader(baseDocReader)
                                         .build();
    }

    private static PropertyScanner createJacksonPropertyScanner() {
        // to match the spring config without accessing the actual bean containing it
        ObjectMapper jacksonObjectMapper = Jackson2ObjectMapperBuilder.json().build();
        return new JacksonPropertyScanner(jacksonObjectMapper);
    }
}
joffrey-bion commented 7 years ago

Closing this issue as it contains 2 different issues: https://github.com/joffrey-bion/livedoc/issues/43 and https://github.com/joffrey-bion/livedoc/issues/44