question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Optional<LocalDate> in model parameter not rendered as String

See original GitHub issue

Version : Download

What kind of issue is this?

  • Question. Is this a question about how to do a certain thing? (May be)
  • Bug report. (may be)
    • Repository with minimal code for reproduction : link

Hi,

I don’t know if it is a bug or a misconfiguration but when i use an Òptional<LocalDate>inside a model like this (all the code is available in the linked repo) :

// Model
public class Modele {

        @DateTimeFormat(pattern = SwaggerConfiguration.LOCAL_DATE_IN_URL)
	private Optional<LocalDate> date;

         public Optional<LocalDate> getDate() {
		return date;
	}

	public void setDate(Optional<LocalDate> date) {
		this.date = date;
	}
}

// Controller
@Controller
@RequestMapping("/test")
public class SampleController {

	@GetMapping
	@ResponseBody
	public ResponseEntity<Object> test(@RequestParam(name = "date", required=false) @DateTimeFormat(pattern = SwaggerConfiguration.LOCAL_DATE_IN_URL) Optional<LocalDate> date) {
		return new ResponseEntity<>(HttpStatus.OK);
	}
	
	@GetMapping("/model")
	@ResponseBody
	public ResponseEntity<Object> test(Modele modele) {
		return new ResponseEntity<>(HttpStatus.OK);
	}
}

// Configuration
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {

	public static final String LOCAL_DATE_IN_URL = "dd-MM-yyyy";
	public static final String LOCAL_DATE_TIME_IN_URL = "yyyy-MM-dd HH:mm:ss";

	@Bean
	public Docket api() {
		return new Docket(DocumentationType.SWAGGER_2)
			.select()
			.apis(RequestHandlerSelectors.any())
			.paths(PathSelectors.any())
			.build()
			.genericModelSubstitutes(Optional.class)
			.apiInfo(apiInfo());
	}

	@Bean
	public FormattingConversionService conversionService() {
		DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);

		DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
		registrar.setDateFormatter(DateTimeFormatter.ofPattern(LOCAL_DATE_IN_URL));
		registrar.setDateTimeFormatter(DateTimeFormatter.ofPattern(LOCAL_DATE_TIME_IN_URL));
		registrar.registerFormatters(conversionService);

		return conversionService;
	}

	private ApiInfo apiInfo() {
		return new ApiInfoBuilder().title("FBO Spring REST Sample with Swagger")
			.description("FBO Spring REST Sample with Swagger")
			.termsOfServiceUrl("https://groupefbo.com/")
			.version("1.0.0")
			.build();
	}
}

It works as expected when using directly an Optional<LocalDate> as a @RequestParam or when using a plain LocalDate field in the model. image

But when i use an Optional<LocalDate> as a field the ouput is : image

Is there a configuration that can solve my issue ?

Thanks !

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:7

github_iconTop GitHub Comments

1reaction
bbeauvaiscommented, Jul 8, 2019

Yes, it did ! Thanks. (Sorry for a long time before answering, the project was dropped off and I forgot)

1reaction
LukeButterscommented, May 8, 2019

ok:


import java.util.List;
import java.util.Optional;
import java.util.Set;

import java.lang.reflect.Type;
import java.net.URI;

import org.springframework.context.annotation.Bean;

import com.fasterxml.classmate.TypeResolver;

import springfox.documentation.schema.AlternateTypeRule;
import springfox.documentation.schema.AlternateTypeRules;
import springfox.documentation.schema.WildcardType;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spring.web.plugins.Docket;

public class Example {
    
    @Bean
    public Docket swaggerSpringMvcPlugin(ApiInfo apiInfo) {
        TypeResolver resolver = new TypeResolver();
        Docket result = null; // Get your Docket however you do that

       //-----------------------------------LOOK HERE----------------------------------------        
        // Tell swagger that URI is just a string
        addAlternateTypeWithSomeGenerics(result, resolver, URI.class, String.class);
       //---------------------------------------------------------------------------------------------
       // All Optionals are unpacked and just returned as either the object or null.
        result.alternateTypeRules(new AlternateTypeRule(
            resolver.resolve(Optional.class, WildcardType.class),
            resolver.resolve(WildcardType.class)));
        
        return result;
    }
    
    /**
     * Tell swagger that we when the JSON is returned the original object will actually look like alternate
     * e.g. ZonedDateTime.class will be flattened into a String.class
     * 
     * Use this method when you want to do this conversion for Set<OriginalType>, List<OriginalType>,
     * Optional<OriginalType> otherwise swagger wont do the conversion. I am not sure why.
     * 
     * This doesn't work for Map. I think Map will be a pain because we have to tell it what to do
     * when the key is one type and the value is another type but we would want to handle
     * Map<CollectionId, ZonedDateTime> in such a way that CollectionId maps to string by one rule
     * and ZonedDateTime maps to string by another rule but swagger doesn't allow two rules to apply 
     * to one thing! I think we would need to do:
     * https://github.com/springfox/springfox/issues/2287#issuecomment-373990884
     * for maps.
     * 
     * Maybe in the future we would need to do it anyway depending how complicated the responses get.
     * 
     * @param docket
     * @param typeResolver
     * @param original
     * @param alternate
     */
    public void addAlternateTypeWithSomeGenerics(Docket docket, TypeResolver typeResolver, Type original, Type alternate) {
        // For all examples we will pretend original is Date and alternate is String
        // to make it easier to understand what is happening 
        
        // e.g. if swagger sees Date convert it to String
        docket.alternateTypeRules(AlternateTypeRules.newRule(original, alternate));
        
        // e.g. if swagger sees Set<Date> convert it to Set<String>
        docket.alternateTypeRules(
                new AlternateTypeRule(typeResolver.resolve(Set.class, original), 
                                    typeResolver.resolve(Set.class, alternate)));
        
        // e.g. if swagger sees List<Date> convert it to List<String>
        docket.alternateTypeRules(
            new AlternateTypeRule(typeResolver.resolve(List.class, original), 
                                typeResolver.resolve(List.class, alternate)));
        
        // We will later tell swagger that optionals are all unpacked but remember swagger is not recursive so
        // we must again tell it for this specific type what it unpacks to e.g. Optional<Date> maps to String
        // e.g. if swagger sees Optional<Date> convert it to String
        docket.alternateTypeRules(
            new AlternateTypeRule(typeResolver.resolve(Optional.class, original), 
                                typeResolver.resolve(alternate)));
    }
}

Note that a big down side exists where you get ride of some type information e.g. your api doc know longer knows what part of a model is a URI for example it only knows it is a String. This is the pain I have https://github.com/springfox/springfox/issues/2987 that might not be an issue for you though.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Why should Java 8's Optional not be used in arguments
Although it might be tempting to consider Optional for not mandatory method parameters, such a solution pale in comparison with other possible alternatives....
Read more >
Model parameters—ArcGIS Pro | Documentation
Any model variable can be set as a model parameter. Set a variable as a ... If the variable has no value in...
Read more >
Component Parameters - Apache Tapestry
Components can have any number of render variables. Render variables are named values with no specific type (they are ultimately stored in a...
Read more >
Query String variables not showing up in URL after a RedirectTo
I am trying to set up a simple link, where the HREF points to a URL with a collection of query string parameters:...
Read more >
Groovy Language Documentation
This extra lenience allows using method or variable names that were not keywords in ... class Car { String make String model }...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found