Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ObjectToObjectConverter doesn't consider return type of static methods #28609

Closed
philwebb opened this issue Jun 10, 2022 · 2 comments
Closed
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: bug A general bug
Milestone

Comments

@philwebb
Copy link
Member

philwebb commented Jun 10, 2022

The ObjectToObject converter currently checks the return type for non-static methods; however, it does no such check for static methods. This means that it's possible for canConvert to return true when the actual conversion would throw an exception.

The following test shows the problem:

public class ObjectToObjectConverterTests {

	@Test
	void fromWithDifferentReturnType() {
		GenericConversionService conversionService = new GenericConversionService();
		conversionService.addConverter(new ObjectToObjectConverter());
		// assertThat(conversionService.canConvert(String.class, Data.class)).isFalse();
		Data data = conversionService.convert("test", Data.class);
	}

	static class Data {

		private final String value;

		private Data(String value) {
			this.value = value;
		}

		@Override
		public String toString() {
			return this.value;
		}

		public static Optional<Data> valueOf(String string) {
			return (string != null) ? Optional.of(new Data(string)) : Optional.empty();
		}

	}

}
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jun 10, 2022
@sbrannen sbrannen self-assigned this Jun 11, 2022
@sbrannen sbrannen added type: bug A general bug in: core Issues in core modules (aop, beans, core, context, expression) and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jun 11, 2022
@sbrannen sbrannen added this to the 5.3.21 milestone Jun 11, 2022
@sbrannen
Copy link
Member

sbrannen commented Jun 12, 2022

The following test cases pass with the change (to verify compatible return type for static factory methods).

class ObjectToObjectConverterTests {

	private final GenericConversionService conversionService = new GenericConversionService() {{
		addConverter(new ObjectToObjectConverter());
	}};


	/**
	 * This test effectively verifies that the {@link ObjectToObjectConverter}
	 * was properly registered with the {@link GenericConversionService}.
	 */
	@Test
	void nonStaticToTargetTypeSimpleNameMethodWithMatchingReturnType() {
		assertThat(conversionService.canConvert(Source.class, Data.class))
			.as("can convert Source to Data").isTrue();
		Data data = conversionService.convert(new Source("test"), Data.class);
		assertThat(data).asString().isEqualTo("test");
	}

	@Test
	void nonStaticToTargetTypeSimpleNameMethodWithDifferentReturnType() {
		assertThat(conversionService.canConvert(Text.class, Data.class))
			.as("can convert Text to Data").isFalse();
		assertThat(conversionService.canConvert(Text.class, Optional.class))
			.as("can convert Text to Optional").isFalse();
		assertThatExceptionOfType(ConverterNotFoundException.class)
			.as("convert Text to Data")
			.isThrownBy(() -> conversionService.convert(new Text("test"), Data.class));
	}

	@Test
	void staticValueOfFactoryMethodWithDifferentReturnType() {
		assertThat(conversionService.canConvert(String.class, Data.class))
			.as("can convert String to Data").isFalse();
		assertThatExceptionOfType(ConverterNotFoundException.class)
			.as("convert String to Data")
			.isThrownBy(() -> conversionService.convert("test", Data.class));
	}


	static class Source {

		private final String value;

		private Source(String value) {
			this.value = value;
		}

		public Data toData() {
			return new Data(this.value);
		}

	}

	static class Text {

		private final String value;

		private Text(String value) {
			this.value = value;
		}

		public Optional<Data> toData() {
			return Optional.of(new Data(this.value));
		}

	}

	static class Data {

		private final String value;

		private Data(String value) {
			this.value = value;
		}

		@Override
		public String toString() {
			return this.value;
		}

		public static Optional<Data> valueOf(String string) {
			return (string != null) ? Optional.of(new Data(string)) : Optional.empty();
		}

	}

}

But... DefaultConversionServiceTests.convertObjectToStringWithJavaTimeOfMethodPresent() now fails, since ZoneId.of(String) declares that it returns ZoneId instead of ZoneRegion (which is a subclass of ZoneId).

I believe the new behavior is desired, but this will require some clarification since it may be a breaking change for applications that depend on the current behavior.

@sbrannen sbrannen changed the title ObjectToObject converter doesn't consider return type of static methods ObjectToObjectConverter doesn't consider return type of static methods Jun 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: bug A general bug
Projects
None yet
Development

No branches or pull requests

3 participants