
# 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 `conversionService` bean with a converter for it, and `@Value` can 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`:

- `String` to `int` (also hex `0x1F` and `#1F`), `double`, `num`, `BigInt`, `bool`, `Uri`, `DateTime` (ISO-8601), `Duration` (`10s`, `PT10S`, plain milliseconds), `DataSize` (`10MB`), `RegExp`, `Resource` and `Encoding`. A blank string becomes `null` for most of these targets.
- `bool` from `true`, `on`, `yes`, `1` and `false`, `off`, `no`, `0`, in any case. Anything else fails.
- `int` to `Duration` (milliseconds) and to `DataSize` (bytes); conversions between `num`, `int`, `double` and `BigInt`.
- Anything to `String`. An enum gives its name, a list `a,b`, a `Duration` and a `DateTime` ISO-8601.
- `List` and `Set` of `String`, `int`, `double`, `num`, `bool`, `BigInt`, `Duration`, `DataSize` and `Uri`, from a comma-separated string, an iterable, or a single value.
- `Map<String, V>` for those element types and for `Object?`.

## Example

```dart
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:

```dart
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 is `null` for 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)

```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>` and `List<String>` are different targets, and each element type needs `addCollectionConverters<E>()`. Spring uses `TypeDescriptor` for generic targets.
- Enums must be registered with `addEnum`; Spring converts every enum without registration.
- Converters are plain functions; there is no `Converter` interface to implement.
- `canConvert` checks a source value; `canConvertType<S>` is Spring's `canConvert(Class, Class)`.
- `Duration` and `DataSize` parsing is built into `DefaultConversionService`. In Spring it lives in Spring Boot's `ApplicationConversionService`.

## See also

- [DataSize](data-size.md)
- [Durations](durations.md)
- [@Value](../config/value.md)
- [Environment](../config/environment.md)
- [Bean](../beans/bean.md)
