jsonserializationcrystal-lang

Compilation error in PullParser method when using UInt128 field, not with UInt64, in Crystal


I have a strange error in crystal lang: if I make a serializable class with the field _timestamp as UInt128, the compilation it causes and error of JSON::PullParsermethod:

$ crystal build src/myprogram.cr 
Showing last frame. Use --error-trace for full trace.

In /usr/lib/crystal/int.cr:1571:11

 1571 | value.to_u128
              ^------
Error: undefined method 'to_u128' for JSON::PullParser

but when you replace UInt128 to UInt64 the compilation error is gone. Is it my fault oer is it a bug in the library?

My program is this:

require "json"

module Tb2md
  VERSION = "0.1.0"

  enum Priority
      Low # 1
      Medium # 2
      High # 3
  end

  class Entry
      include JSON::Serializable
      property _id : UInt32
      property _date : Time
      property _timestamp : UInt128
      property description : String
      property isStarred : Bool
      property boards : Array(String)
      property _isTask : Bool
      property isComplete : Bool
      property inProgress : Bool
      property priority : Priority
  end

  class Habitat
      include JSON::Serializable
      property entries : Hash(String, Entry)
  end

  content = File.open("storage.json") do |file|
      file.gets_to_end
  end

  h = Habitat.from_json(content)
  puts h
end

Solution

  • JSON is historically derived from JavaScript, which treated numbers greater then 53 bits as floating point numbers (it only later added support for BigInt). I believe that is the reason why the Crystal JSON implementation rightfully rejects UInt128; otherwise, it risks to be incompatible to other implementations. Even 64 bit numbers are a grey area if more then 53 bits are used. If you need to represent big integers precisely and in a portable way, you have to encode them as strings in JSON.

    This is a minimal reproducible example (Uint128 fails to compile, but UInt64 or Float64 will work):

    require "json"
    
    class Entry
      include JSON::Serializable
      property x : UInt128  # <-- will not compile
    end
    
    puts (Entry.from_json("{ \"x\": 1 }").to_json)