[Ruby] Why require'json'to enable to_json

TL;DR

--Since the C language extension method is called when the json module is loaded, the function is added and the module is added, and the built-in class is mixed later.

Question

When converting to json in Ruby, I think that you often use to_json. When converting from an object of type Hash or ʻArray, write the following code, but if you do not do require'json', an error will occur with NoMethodError`.

--require'json' not

Error occurs


irb(main):002:0> some_hash = {a: 1,b: 2}
irb(main):004:0> some_hash.to_json()
Traceback (most recent call last):
        5: from /usr/local/opt/ruby/bin/irb:23:in `<main>'
        4: from /usr/local/opt/ruby/bin/irb:23:in `load'
        3: from /usr/local/Cellar/ruby/2.7.1_2/lib/ruby/gems/2.7.0/gems/irb-1.2.3/exe/irb:11:in `<top (required)>'
        2: from (irb):3
        1: from (irb):4:in `rescue in irb_binding'
NoMethodError (undefined method `to_json' for {:a=>1, :b=>2}:Hash)
Did you mean?  to_s

--require'json'

No error occurs


irb(main):005:0> require 'json'
=> true
irb(main):002:0> some_hash = {a: 1,b: 2}
irb(main):006:0> some_hash.to_json()
=> "{\"a\":1,\"b\":2}"

I found it unusual to add instance methods to the built-in class later, so I'll follow the internal implementation.

Source code check

According to the Ruby documentation, there seems to be a module called JSON :: Generator :: GeneratorMethods :: Hash.

https://docs.ruby-lang.org/ja/latest/class/JSON=3a=3aGenerator=3a=3aGeneratorMethods=3a=3aHash.html

If you search the relevant part of the Ruby source code, it seems that you are probably calling each generator class (json / ext / generator) from here.

https://github.com/ruby/ruby/blob/v2_7_1/ext/json/lib/json/ext.rb#L8

ext.rb


  module Ext
    require 'json/ext/parser'
    require 'json/ext/generator'
    $DEBUG and warn "Using Ext extension for JSON."
    JSON.parser = Parser
    JSON.generator = Generator
  end

If you check the following further, you can see that json / ext / generator is compiled as an extension library from C language instead of .rb.

https://github.com/ruby/ruby/blob/v2_7_1/ext/json/generator/extconf.rb#L4

extconf.rb


require 'mkmf'

$defs << "-DJSON_GENERATOR"
create_makefile 'json/ext/generator'

When I checked the corresponding c language file, I found that rb_define_method registered the to_json method and module for the Hash class.

https://github.com/ruby/ruby/blob/68d7e93b3baf91ac7d7cc100b75bab81ba7dee76/ext/json/generator/generator.c#L1511

    mHash = rb_define_module_under(mGeneratorMethods, "Hash");
    rb_define_method(mHash, "to_json", mHash_to_json, -1);

And it seems that the above module is adding an instance method later by mixing it below.

https://github.com/ruby/ruby/blob/68d7e93b3baf91ac7d7cc100b75bab81ba7dee76/ext/json/lib/json/common.rb#L67

       klass.class_eval do
          instance_methods(false).each do |m|
            m.to_s == 'to_json' and remove_method m
          end
          include modul
        end

Summary

I found out that when I read the json module, I called the C language extension method to add a function and a module, and later mixed it into the built-in class.

It was a little refreshing because some languages (such as Python) do not allow the addition of methods to built-in classes. You can flexibly inject into embedded classes, for better or for worse. I would like to continue studying.

Recommended Posts

[Ruby] Why require'json'to enable to_json
Why Schools Learn Ruby
Divide by Ruby! Why is it 0?