LoginSignup
1
0

More than 5 years have passed since last update.

ゲームっぽい何かを作りながらRubyを勉強する その3 〜JSON〜

Last updated at Posted at 2016-11-30

今回はmygameリポジトリに何か実装を追加した、という話ではないのですが、調査としてRubyにおけるJSONの扱いについて調べてみましたので、わかったことをメモします。

見たもの

とりあえず日本語のリファレンスマニュアルは一通り読んでみました。
loadで文字列 -> オブジェクトの変換、dumpでオブジェクトからJSON文字列への変換とのこと。ふむふむ。

ついでにRubyで提供されているAPIはソースコードが割と簡単に読めるということをpryしてたら気づいたので(エラーにファイルパスが書いてあった)、読んでみました。GitHubにも上がっていますので、Web上で見たい場合ば上記のリンクから。バージョンの違いだけ注意です。

分かったこと

loadに渡すオブジェクトは文字列以外もいける

リファレンスマニュアルには

JSON 形式の文字列を指定します。他には、to_str, to_io, read メソッドを持つオブジェクトも指定可能です。

と書いてあり、実際ソースコードを読んでも

common.rb
def load(source, proc = nil, options = {})
    opts = load_default_options.merge options
    if source.respond_to? :to_str
      source = source.to_str
    elsif source.respond_to? :to_io
      source = source.to_io.read
    elsif source.respond_to?(:read)
      source = source.read
    end
    if opts[:allow_blank] && (source.nil? || source.empty?)
      source = 'null'
    end
    result = parse(source, opts)
    recurse_proc(result, &proc) if proc
    result
end

と書いてあり、最初にsourceto_str, to_io, readメソッドを持っているかを(左の順番で)調べ、あればそれを使ってJSON文字列を得ようとしている。

つまり、sourceとして与えるのは文字列だけでなくPathname.pwd.join('hoge.txt')などで得られたファイルオブジェクトをそのまま渡しても問題ない、ということになる。

pry
pry(main)> require 'pathname'
pry(main)> require 'json'

pry(main)> JSON.load(Pathname.pwd.join('sample_json.txt'))
=> {"hoge"=>111}

dumpするときのネストの深さはデフォルトで19まで無限

unparseメソッドの説明に、

:max_nesting
入れ子になっているデータの最大の深さを指定します。 偽を指定すると深さのチェックを行いません。デフォルトは 19 です。

と書いてある通り、デフォルトでは入れ子のデータの最大の深さはデフォルトでは19になっている。これはunparseの説明だが、dumpの場合も中でJSON.#generateを呼び出していて、そこにはunparseと同じ説明が書いてあるので、 やはりデフォルト上限は19になっている。

(2016/11/30 追記)
違いました。
以下に引用してるソースコードにも書いてありますが、指定がない場合はJSON.dump_default_optionsを使っていて、その定義は以下のようになっています。

common.rb
self.dump_default_options = {
  :max_nesting => false,
  :allow_nan   => true,
}

というわけで、dumpメソッドを使った場合のデフォルトのmax_nestingfalseつまり「チェック無し」でした。
(/追記)

この制限を取り外したい場合は

rb: pry
pry(main)> JSON.dump({:hoge => 111}, limit = 100)=> {"hoge"=>111}

というように引数でlimitを指定する。
と、dumpメソッドは中で、

common.rb
def dump(obj, anIO = nil, limit = nil)

...省略

  opts = JSON.dump_default_options
  opts = opts.merge(:max_nesting => limit) if limit
  result = generate(obj, opts)

...省略

end

というように与えられたlimitgenerate:max_nestingオプションとしてそのまま渡している。
おそらくlimitに数値ではなくfalseを与えれば、上記の引用部分に書かれている通り入れ子のチェック自体が行われなくなる。と思う。

循環参照に注意

:check_circular
真を指定した場合、生成するオブジェクトの循環をチェックします。 この動作がデフォルトです。

というオプションがある通り、dumpする際は与えるオブジェクトの循環参照に注意する。

これはRubyに限った話ではないような気もするが、Javaではクラス定義だけ見ていれば循環参照が起こり得るかすぐに分かる上、そもそもデータを表すクラスで循環参照しようと思ったこともなかったので今まで意識していなかったのですが、改めてなるほどと思ったのでメモ。

ちなみに循環参照が起こりえない自信がある場合はチェックそのものを行わない(つまり {:check_circular => false}が指定された)fast_generateというメソッドもある。fastというくらいなのでたぶんdumpよりも早い。でもlimitなどは指定できない。

と思って念のため実装みたら、第2引数にoptsを渡せば他のオプションについても指定可能な様子。

common.rb
def fast_generate(obj, opts = nil)
  if State === opts
    state, opts = opts, nil
  else
    state = FAST_STATE_PROTOTYPE.dup
  end
  if opts
    if opts.respond_to? :to_hash
      opts = opts.to_hash
     elsif opts.respond_to? :to_h
      opts = opts.to_h
    else
      raise TypeError, "can't convert #{opts.class} into Hash"
    end
    state.configure(opts)
  end
  state.generate(obj)
end

理解が間違っていなければ、「optsStateでなければ、'FAST_STATE_PROTOTYPE'(:max_nesting => falseが指定されたSTATE)をベースに、optsの設定を追加する」という実装になっているので、他のオプションについても上書きで指定が可能なようです。

実装見ないとメソッドにどんな引数を渡せば良いかも分からない部分があるのが、Javaに慣れている身としては慣れないところです。

(と思ったらrdocにはちゃんと書いてありました。うむむ。。。)

まとめ

というわけで、RubyでJSONを扱う際の基本を調べてみました。
ゲームっぽい何かのセーブデータなどを扱う上で使うような気がしたので軽く調べてみましたが、意外とドキュメントやソースコード、pryでの動作確認などを使えば調べるのは簡単で、分かったことも多かった印象です。

まだまだ「Rubyでこれやる場合はどう書けばいいんだろう」の疑問は数多くありますので(DBアクセスとかHTTP通信とか)、引き続き調べつつ実際に使いつつ、Ruby自体に慣れていければと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0