Magnolia Tech

いつもコードのことばかり考えている人のために。

小さなPRを書いてOSS開発に貢献する

OSS開発に参加してみたいですよね!(断定)

でもどこから手をつけて良いか分からないことも多いですね。

そこでScalaのjson4sというライブラリをベースに、小さなPRを書いて、送るまでの流れを書いてみました。

参考にしてみて下さい。

今回は非推奨になったメソッドの置き換えを3カ所(3行)に対して行いました。とても短いPRですが、これで確実にJson4sをコンパイルする度に出ていた非推奨メソッドの警告メッセージが出なくなり、また今後のメンテナンス性も向上しました(非推奨になったメソッドはいつ削除されるか分からないので)。

GitHubからソースコードをcloneする

まずは(あとでPRを送るため)GitHub上で元のリポジトリをForkします。

GitHubの画面の右上にあるForkボタンを押してみてください。

自分のリポジトリにForkできたら、今度はそこから手元のローカル環境にcloneします。自分のアカウント(magnolia-k)では、以下のようになります。自分のアカウントに合わせて書き換えてください。

$ git clone git@github.com:magnolia-k/json4s.git

compileを実行する

sbtを起動して、compileを実行します。

(詳しいScalaでの開発方法は割愛します。その辺りは、Scalaの入門書などを参照して下さい)

すると、以下のようなメッセージが表示されました(のちほど出てくるPRがマージされると…残念ながらもう出なくなりますが)。

[warn] /home/xxx/json4s/jackson/src/main/scala/org/json4s/jackson/JValueDeserializer.scala:50:28: method mappingException in class DeserializationContext is deprecated: see corresponding Javadoc for more information.
[warn]       case _ => throw ctxt.mappingException(classOf[JValue])
[warn]                            ^
[warn] /home/xxx/json4s/jackson/src/main/scala/org/json4s/jackson/JValueDeserializer.scala:53:61: method mappingException in class DeserializationContext is deprecated: see corresponding Javadoc for more information.
[warn]     if (!klass.isAssignableFrom(value.getClass)) throw ctxt.mappingException(klass)
[warn]                                                             ^
[warn] /home/xxx/json4s/jackson/src/main/scala/org/json4s/jackson/JsonMethods.scala:20:25: method reader in class ObjectMapper is deprecated: see corresponding Javadoc for more information.
[warn]     var reader = mapper.reader(classOf[JValue])
[warn]                         ^
[warn] three warnings found
[info] Done compiling.

警告メッセージの意味を理解する

では実際に出た警告メッセージの理解し、対処方法を探って行きましょう。

順番が前後しますが、まずは下記のメッセージから…

method reader in class ObjectMapper is deprecated: see corresponding Javadoc for more information.

「see corresponding Javadoc for more information.」って書かれていて、具体的な場所を示してくれよ!って一瞬思いますが、そんな気持ちをぐっと堪えて、javadocを探します。

ObjectMapperJson4sが利用しているJackson-databindというJsonライブラリが持つclassです。

不要な箇所を削除しているので、完全に同じソースコードでは有りませんが…mapper.reader(classOf[JValue])readerメソッドの呼び出し箇所で警告が出ていて、そのreaderメソッドがObjectMapperというclassのメソッドであることが分かるでしょう(mapperの中身が_defaultMapperメソッドの中で生成されたObjectMapper)。

import com.fasterxml.jackson.databind._

trait JsonMethods extends org.json4s.JsonMethods[JValue] {

  private[this] lazy val _defaultMapper = {
    val m = new ObjectMapper()
    m.registerModule(new Json4sScalaModule)

    m
  }
  def mapper = _defaultMapper

  def parse(in: JsonInput, useBigDecimalForDouble: Boolean = false, useBigIntForLong: Boolean = true): JValue = {
    var reader = mapper.reader(classOf[JValue])

readerメソッドのjavadocは下記のURLで参照できます。

ObjectMapper (jackson-databind 2.9.0 API)

javadocには以下のように書かれています。

ObjectReader reader(Class<?> type)
Deprecated. 
Since 2.5, use readerFor(Class) instead

つまり、readerForというメソッドに置き換えれば良いことが分かります。

ちなみに今回は、結果的にreaderForで単純に置き換えれば良かったのですが、そもそも「なぜdeprecatedになったのか?」という理由を探すことができませんでした。急にdeprecatedになったので書き換えろ、と言われても…という気持ちになったので、READMEやChangesに書いておいて欲しいな、と思いました。

そもそも設計思想が変わった場合は当然単純な置き換えではいけないので…

次は同じメッセージが2つ出ている警告の方に取りかかります。

method mappingException in class DeserializationContext is deprecated: see corresponding Javadoc for more information.

こちらも

DeserializationContext (jackson-databind 2.9.0 API)

以下のように書かれているので、handleUnexpectedTokenで置き換えれば良いことが分かります。

@Deprecated
public JsonMappingException mappingException(Class<?> targetClass)
Deprecated. Since 2.8 use handleUnexpectedToken(Class, JsonParser) instead
Helper method for constructing generic mapping exception for specified type

しかし、よく見ると元のメソッドと戻りの型が違います。handleUnexpectedTokenObject型を返すのに対して、元のmappingExceptionJsonMappingException型を返しています。当然そのまま置き換えるとコンパイルエラーになります。

handleUnexpectedTokenメソッドのソースを追いかけてみましょう。

public Object handleUnexpectedToken(Class<?> instClass, JsonParser p)
        throws IOException
{
    return handleUnexpectedToken(instClass, p.currentToken(), p, null);
}

public Object handleUnexpectedToken(Class<?> instClass, JsonToken t,
            JsonParser p, String msg, Object... msgArgs)
        throws IOException
{
...
reportInputMismatch(instClass, msg);
return null; // never gets here
}

public <T> T reportInputMismatch(Class<?> targetType,
        String msg, Object... msgArgs) throws JsonMappingException
{
...
    msg = _format(msg, msgArgs);
    throw MismatchedInputException.from(getParser(), targetType, msg);
}

どうやら最終的に例外を送出するコードにしか到達せず、Object型の返り値は便宜的に付けられていることが分かりました。つまり元のコードの呼び出し方法を変えれば良さそうなことが分かります。

元々は例外オブジェクトを返り値で受けて、それをthrowしていました。

case _ => throw ctxt.mappingException(classOf[JValue])

handleUnexpectedTokenメソッドの中でthrowしているので、呼び出し時のthrowを削除します。

case _ => ctxt.handleUnexpectedToken(classOf[JValue], jp)

コミットする

コミットメッセージは、gitのお作法に従って、変更理由を書きましょう。

この記事なんか参考になると思います。

qiita.com

でもtypoの修正とかなら、「fixed typo」だけでも充分です。

PRを送る

修正ができたらPRを送ります。

github.com

終わりに

如何でしたでしょうか?1行のコード修正と言っても、ドキュメントや関連するソースを追いかける必要が有り、サクっと終わるわけではないですが、解決方法が分かると成長した感を実感できますね。

ほかにもドキュメントのtypoを直す、チュートリアルが最新バージョンと合っていない所を直す、サンプルコードを直す・追加する等、貢献できるところは色々と有るので、ぜひチャレンジして見て下さい。

その途中で調べることや試行錯誤することがたくさん出てきて、周辺知識がどんどん身についていくと、単純な修正だけでも得られることがたくさん有ることを実感できるのではないでしょうか。