ohler55 / oj

Optimized JSON
http://www.ohler.com/oj
MIT License
3.14k stars 251 forks source link

Support for Ruby's `Singleton` module? #846

Closed jez closed 10 months ago

jez commented 1 year ago

tl;dr: The oj gem does not work correctly with the Singleton gem.

There is a module called Singleton in the Ruby standard library:

https://ruby-doc.org/3.1.2/stdlibs/singleton/Singleton.html

The idea behind the class is to ensure that there is one instance of a class globally, throughout the whole program.

It marks self.new private so that it cannot be called, and instead forces the instance to be created or accessed via the self.instance method. It also overrides dup and clone so that they return self.

It supports Ruby's Marshal class as well: there are custom definitions of _load and _dump that know how to access the single, already-allocated instance of the class (and respects user-defined overrides that deal with custom instance variable state).

The oj gem does not work correctly with the Singleton gem. Here's an example:

require 'oj'
require 'singleton'

class A
  include Singleton
end

serialized = Oj.dump(A.instance)
p(serialized)
puts
x_deser = Oj.load(serialized)
p(x_deser)
p(x_deser.__id__)
p(A.instance)
p(A.instance.__id__)
p(x_deser.eql?(A.instance))

On my machine, this prints:

"{\"^o\":\"A\"}"

#<A:0x000055db78ce95d8>
60
#<A:0x000055db78ce9740>
80
false

I've spent a lot of time reading the docs and looking at the source code, and I can't figure out a way to customize oj to re-use the already-created instance of A.

Is there a way to configure oj to make the x_deser.eql?(A.instance) check (which is doing reference equality) evaluate to true?


I've shown this example using the Singleton module in the Ruby standard library to be as minimal as possible.

The example that I actually care more about is a custom class for creating typed enum values, where there should be a single instance of each enum value, so that enum values of a given class can be compared quickly for equality.

ohler55 commented 1 year ago

You might try the register_odd function otherwise I'd have to think a little more deeply about it.