YouTube Data API の 戻り値を Jackson を使用して Map に変換し、結果を MongoDB に登録しようとしたところ、以下のエラーが発生しました。
org.bson.codecs.configuration.CodecConfigurationException:
Can't find a codec for class com.google.api.client.util.DateTime.
Codec が見つからないということなので、Codec を実装し登録してみます。
参考サイト
Library version
関連 Liblary の Version は以下の通りです。
- YouTube Data API (v3)
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-youtube</artifactId>
<version>v3-rev178-1.22.0</version>
</dependency>
- Java Mongo Driver
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.2.2</version>
</dependency>
- Jackson
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.6.3</version>
</dependency>
そもそもの発生理由 (推測)
何故、Codec でエラーとなるのか推測ではありますが、処理の流れとともに記載します。
1.処理の流れ
以下のような処理の流れで、YouTube Data API (v3) の取得結果を、MongoDB に登録しています。
1-1. YouTube Data API で 検索、戻り値 の List<SearchResult>
を取得
YouTube Data API で 検索後、 戻り値 を SearchResult の List 形式で取得しています。
1-2. SearchResult を Jackson を 使用して、Map<String, Object>
に変換
ObjectMapper を使用して Map に変換しています。
/**
* convertValue
*
* @param <T>
* @param object
* @param clazz
* @return
*/
public static Map<String, Object> convertValue(Object object) {
ObjectMapper objectMapper = new ObjectMapper();
T result = objectMapper.convertValue(object, Map.class);
return result;
}
1-3. MongoDriver で、 2 で変換生成した Map を MongoDB に insert
Map を BasicDBObject に渡して登録しています。
2.SearchResult のどこに、DateTime が存在するか?
Class 図は以下のようになります。
3.MongoDriver は Codec 登録されていない Object の処理はできない。
基本的な型の Object については、Mongo Driver 側が Codec を保持しており、変換できます。
しかし 末端の階層の Object に Java の基本型を持たない Object は変換できないようです。
SearchResult は、内部で ResourceId というクラスも参照していますが、こちらは、内部のフィールドが String 型のみなので、変換が可能です。
mongo-java-driver/bson/src/main/org/bson/codecs at master · mongodb/mongo-java-driver
にデフォルトで登録されている(と思われる) Codec クラスが存在します。
4.うまく動かす方法
修正の仕方として、以下2つ考えられます。
1.ObjectMapper で Mapping する際に、DateTime をあらかじめ MongoDB が解釈できる型に変えておく。
2.解釈できない型の Codec を登録する。
今回は 2.
の Codec 登録する方法を試してみました。
Codec クラスを作成 と 登録
1. Codec クラスを作成する
以下の通り、Codec クラスを作成しました。
String 型への相互変換用の、DateTime#parseRfc3339()
、DateTime#toStringRfc3339()
がいましたのでそれを使用しています。
- GoogleDateTimeCodec.java
import com.google.api.client.util.DateTime; import org.bson.BsonReader; import org.bson.BsonWriter; import org.bson.codecs.Codec; import org.bson.codecs.DecoderContext; import org.bson.codecs.EncoderContext; /** * GoogleDateTimeCodec */ public class GoogleDateTimeCodec implements Codec<DateTime> { @Override public DateTime decode(BsonReader reader, DecoderContext decoderContext) { String date = reader.readString(); return DateTime.parseRfc3339(date); } @Override public void encode(BsonWriter writer, DateTime value, EncoderContext encoderContext) { writer.writeString(value.toStringRfc3339()); } @Override public Class<DateTime> getEncoderClass() { return DateTime.class; } }
2. Codecクラスの登録
Guice の Provider を使用しています。
MongoClient の インスタンス生成時に、MongoClientOptions をパラメータとして設定、MongoClientOptions に設定する CodecRegistry に GoogleDateTimeCodec を 追加 しました。
MongoDB に 登録する末端フィールドの変換ができないだけであれば、以下のような登録の仕方でうまくいきました。
末端フィールドではない場合は、StackOverFlowにある通り、Object の依存関係に従って登録する必要がありそうです。
- MongoDatabaseProvider.java
import com.google.inject.Provider; import com.mongodb.MongoClient; import com.mongodb.MongoClientOptions; import com.mongodb.ServerAddress; import com.mongodb.client.MongoDatabase; import org.bson.codecs.configuration.CodecRegistries; import org.bson.codecs.configuration.CodecRegistry; import xyz.monotalk.festivals4partypeople.models.mongo.codec.GoogleDateTimeCodec; import java.util.Locale; import java.util.ResourceBundle; /** * MongoDatabaseProvider * * @author Kem */ public class MongoDatabaseProvider implements Provider<MongoDatabase> { private static final ResourceBundle RB = ResourceBundle.getBundle("mongoDb", Locale.JAPAN); @Override public MongoDatabase get() { // New ServerAddress ServerAddress serverAddress = newServerAddress(); // New MongoClientOptions MongoClientOptions options = newMongoClientOptions(); MongoClient client = new MongoClient(serverAddress, options); // New Database String databaseName = RB.getString("databaseName"); MongoDatabase mongoDatabase = client.getDatabase(databaseName); return mongoDatabase; } /** * newServerAddress * * @return */ private ServerAddress newServerAddress() { String host = RB.getString("host"); int port = Integer.valueOf(RB.getString("port")); ServerAddress serverAddress = new ServerAddress(host, port); return serverAddress; } /** * newMongoClientOptions * * @return */ private MongoClientOptions newMongoClientOptions() { CodecRegistry codecRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(new GoogleDateTimeCodec()), MongoClient.getDefaultCodecRegistry()); return MongoClientOptions.builder() .codecRegistry(codecRegistry).build(); } }
以上です。
コメント