
# ConversionService

`ConversionService` は、ある値を別の型の値に変えます。たとえば設定ファイルのテキスト `'10s'` を `Duration` にします。

## 使う理由

フレームワークは型付きの値を埋めるたびに変換サービスを呼びます。`@Value` の注入、`PropertyResolver.getPropertyAs<T>`、`@ConfigurationProperties` のバインドです。`DefaultConversionService` は一般的な型をすでに知っているため、ほとんどのアプリケーションは結果を受け取るだけです。

自分で使うのは次の 2 つの場合です。

- **独自の型。** その型のコンバーターを持つ `conversionService` Bean を宣言すると、`@Value` でその型を注入できます。この Bean は祖先のコンテキストにあっても見つかります。
- **enum。** リフレクションがないため、Dart には値の一覧が必要です。`addEnum(MyEnum.values)` で登録します。

自分のコードで値を変換するときも `convert<T>` を直接呼びます。

`DefaultConversionService` に組み込まれている変換:

- `String` から `int`(16 進の `0x1F`、`#1F` も可)、`double`、`num`、`BigInt`、`bool`、`Uri`、`DateTime`(ISO-8601)、`Duration`(`10s`、`PT10S`、単位なしはミリ秒)、`DataSize`(`10MB`)、`RegExp`、`Resource`、`Encoding`。これらの多くでは、空白だけの文字列は `null` になります。
- `bool` は `true`、`on`、`yes`、`1` と `false`、`off`、`no`、`0` を大文字小文字を問わず受け付けます。それ以外は失敗します。
- `int` から `Duration`(ミリ秒)と `DataSize`(バイト)。`num`、`int`、`double`、`BigInt` 間の変換。
- あらゆる値から `String`。enum は名前、リストは `a,b`、`Duration` と `DateTime` は ISO-8601 になります。
- `String`、`int`、`double`、`num`、`bool`、`BigInt`、`Duration`、`DataSize`、`Uri` の `List` と `Set`。カンマ区切りの文字列、Iterable、単一の値から変換します。
- それらの要素型と `Object?` の `Map<String, V>`。

## 例

```dart
import 'package:spring_core_dart/spring_core_dart.dart';

/// 金額。設定ファイルでは `12.50 EUR` と書く。
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 {
  // コンテキストはこの Bean で @Value の引数を変換するので、
  // `@Value(r'${shop.min-order}') Money minOrder` が使える。
  @Bean()
  ConversionService conversionService() => DefaultConversionService()
    ..addConverter<String, Money>(Money.parse)
    ..addCollectionConverters<Money>() // List<Money> と Set<Money>
    ..addEnum(Level.values); // `warn`、`WARN`、`Warn` のどれでも一致する
}

@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;
}
```

コードでの変換:

```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'
```

エラー:

- `ConversionFailedException`: コンバーターはあるが値を解析できない、または null 非許容の型に対して結果が `null` になった。
- `ConverterNotFoundException`: その型を対象とするコンバーターがない。

## メンバー

`ConversionService`:

| 名前 | 戻り値 | 意味 |
|---|---|---|
| `convert<T>(source)` | `T` | `T` に変換する。すでに `T` ならそのまま返す。 |
| `convertTo(source, targetType)` | `Object?` | 実行時に `Type` 値としてだけ分かる型に変換する。 |
| `canConvert(source, targetType)` | `bool` | この値を受け付けるコンバーターがあるか。値が解析できるかは確認しない。 |
| `canConvertType<S>(targetType)` | `bool` | 値がまだない段階で、型 `S` の値を変換できるか。 |

`ConverterRegistry`(`GenericConversionService` と `DefaultConversionService` が実装):

| 名前 | 意味 |
|---|---|
| `addConverter<S, T>(converter)` | `S` から `T` ちょうどへの関数を追加する(`T?` にも使われる)。同じ型への後の登録が優先される。 |
| `addEnum<E>(values)` | enum を `String` との間で、また `List<E>` / `Set<E>` として変換できるようにする。名前は大文字小文字と区切り文字を無視して照合する。 |
| `addCollectionConverters<E>()` | `List<E>` と `Set<E>` を変換できるようにする。 |
| `addMapConverters<K, V>()` | 任意の Map から `Map<K, V>` に変換できるようにする。 |
| `removeConvertible<T>()` | `T` と `T?` を対象とするコンバーターをすべて削除する。 |

`DefaultConversionService.sharedInstance` は、サービスを与えられていないものすべてが使います。これにコンバーターを追加せず、自分のインスタンスを作ってください。

## 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);
```

重要な違い:

- 変換先は Dart の `Type` の完全一致で照合します。`List<int>` と `List<String>` は別の変換先で、要素型ごとに `addCollectionConverters<E>()` が必要です。Spring はジェネリックな変換先に `TypeDescriptor` を使います。
- enum は `addEnum` で登録する必要があります。Spring は登録なしですべての enum を変換します。
- コンバーターはただの関数です。実装すべき `Converter` インターフェースはありません。
- `canConvert` はソースの値を調べます。`canConvertType<S>` が Spring の `canConvert(Class, Class)` に当たります。
- `Duration` と `DataSize` の解析は `DefaultConversionService` に組み込まれています。Spring では Spring Boot の `ApplicationConversionService` にあります。

## 関連項目

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