ConversionService
ConversionService turns a value into a value of another type, such as the text '10s' from a config file into a Duration.
Why use it
The framework calls a conversion service whenever it fills a typed value: @Value injection, PropertyResolver.getPropertyAs<T> and @ConfigurationProperties binding. DefaultConversionService already knows the usual types, so most applications only receive the results.
Use it yourself in two cases:
- A type of your own. Declare a
conversionServicebean with a converter for it, and@Valuecan inject that type. The bean is also found in an ancestor context. - Enums. Without reflection, Dart needs the list of values: register an enum with
addEnum(MyEnum.values).
You also call convert<T> directly to convert values in your own code.
Built into DefaultConversionService:
Stringtoint(also hex0x1Fand#1F),double,num,BigInt,bool,Uri,DateTime(ISO-8601),Duration(10s,PT10S, plain milliseconds),DataSize(10MB),RegExp,ResourceandEncoding. A blank string becomesnullfor most of these targets.boolfromtrue,on,yes,1andfalse,off,no,0, in any case. Anything else fails.inttoDuration(milliseconds) and toDataSize(bytes); conversions betweennum,int,doubleandBigInt.- Anything to
String. An enum gives its name, a lista,b, aDurationand aDateTimeISO-8601. ListandSetofString,int,double,num,bool,BigInt,Duration,DataSizeandUri, from a comma-separated string, an iterable, or a single value.Map<String, V>for those element types and forObject?.
Example
import 'package:spring_core_dart/spring_core_dart.dart';
/// An amount of money, written `12.50 EUR` in config files.
class Money {
const Money(this.amount, this.currency);
final double amount;
final String currency;
static Money parse(String text) {
final [amount, currency] = text.trim().split(' ');
return Money(double.parse(amount), currency);
}
}
enum Level { info, warn, error }
@Configuration()
class ConversionConfig {
// The context uses this bean to convert @Value arguments, so
// `@Value(r'${shop.min-order}') Money minOrder` works.
@Bean()
ConversionService conversionService() => DefaultConversionService()
..addConverter<String, Money>(Money.parse)
..addCollectionConverters<Money>() // List<Money> and Set<Money>
..addEnum(Level.values); // `warn`, `WARN` and `Warn` all match
}
@Service()
class Checkout {
Checkout(
@Value(r'${shop.min-order:10.00 EUR}') this.minOrder,
@Value(r'${shop.alert-level:warn}') this.alertLevel,
);
final Money minOrder;
final Level alertLevel;
}
Converting in code:
final service = DefaultConversionService();
service.convert<int>('0x1F'); // 31
service.convert<bool>('yes'); // true
service.convert<List<int>>('1, 2, 3'); // [1, 2, 3]
service.convert<int?>(''); // null
service.convert<String>(const Duration(seconds: 10)); // 'PT10S'
Errors:
ConversionFailedException: a converter exists but the value does not parse, or the result isnullfor a non-nullable target.ConverterNotFoundException: no converter targets the type.
Members
ConversionService:
| Name | Returns | Meaning |
|---|---|---|
convert<T>(source) |
T |
Converts to T; returns the source unchanged when it already is a T. |
convertTo(source, targetType) |
Object? |
Converts to a target known only as a Type value at run time. |
canConvert(source, targetType) |
bool |
Whether a converter accepts this value. It does not check that the value parses. |
canConvertType<S>(targetType) |
bool |
Whether values of type S can be converted, before any value exists. |
ConverterRegistry (implemented by GenericConversionService and DefaultConversionService):
| Name | Meaning |
|---|---|
addConverter<S, T>(converter) |
Adds a function from S to exactly T (it also serves T?). A later registration for the same target wins. |
addEnum<E>(values) |
Makes an enum convertible from and to String, and as List<E> / Set<E>. Names match ignoring case and separators. |
addCollectionConverters<E>() |
Makes List<E> and Set<E> convertible. |
addMapConverters<K, V>() |
Makes Map<K, V> convertible from any map. |
removeConvertible<T>() |
Removes every converter targeting T and T?. |
DefaultConversionService.sharedInstance is used by anything that was given no service. Do not add converters to it; create your own instance.
In Spring (Java)
@Bean
ConversionService conversionService() {
DefaultConversionService service = new DefaultConversionService();
service.addConverter(String.class, Money.class, Money::parse);
return service;
}
Integer port = service.convert("8080", Integer.class);
Differences that matter:
- Targets are matched by exact Dart
Type.List<int>andList<String>are different targets, and each element type needsaddCollectionConverters<E>(). Spring usesTypeDescriptorfor generic targets. - Enums must be registered with
addEnum; Spring converts every enum without registration. - Converters are plain functions; there is no
Converterinterface to implement. canConvertchecks a source value;canConvertType<S>is Spring'scanConvert(Class, Class).DurationandDataSizeparsing is built intoDefaultConversionService. In Spring it lives in Spring Boot'sApplicationConversionService.