Module: Causalontology::Jcs

Defined in:
lib/causalontology/jcs.rb

Constant Summary collapse

ESCAPES =

The two-character escapes of RFC 8785 section 3.2.2.2.

{
  '"'  => '\"',
  "\\" => "\\\\",
  "\b" => "\\b",
  "\t" => "\\t",
  "\n" => "\\n",
  "\f" => "\\f",
  "\r" => "\\r",
}.freeze

Class Method Summary collapse

Class Method Details

.encode(value) ⇒ Object

The canonical serialization of a parsed JSON value (nil, true/false, Integer, Float, String, Array, or Hash with String keys).



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/causalontology/jcs.rb', line 73

def encode(value)
  case value
  when nil
    "null"
  when true
    "true"
  when false
    "false"
  when Integer, Float
    number(value)
  when String
    string(value)
  when Array
    "[" + value.map { |v| encode(v) }.join(",") + "]"
  when Hash
    items = value.keys.sort_by(&:codepoints)
    "{" + items.map { |k| string(k) + ":" + encode(value[k]) }.join(",") + "}"
  else
    raise TypeError, "cannot canonicalize #{value.class}"
  end
end

.number(n) ⇒ Object

A canonical JSON number: integers verbatim; integer-valued floats below 1e21 printed as integers; other floats in shortest round-trip form with the exponent normalized to ES6 style ("1e-7", "1e+21").

Raises:

  • (ArgumentError)


54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/causalontology/jcs.rb', line 54

def number(n)
  return n.to_s if n.is_a?(Integer)
  raise ArgumentError, "NaN and Infinity are not permitted (RFC 8785)" unless n.finite?
  return "0" if n == 0
  return n.to_i.to_s if n == n.truncate && n.abs < 1e21
  r = n.to_s # shortest round-trip decimal
  if r.include?("e")
    mant, exp = r.split("e")
    mant = mant.sub(/\.0\z/, "") # Ruby prints "1.0e-07"; ES6 prints "1e-7"
    sign = exp.start_with?("-") ? "-" : "+"
    digits = exp.sub(/\A[+-]/, "").sub(/\A0+/, "")
    digits = "0" if digits.empty?
    r = mant + "e" + sign + digits
  end
  r
end

.string(s) ⇒ Object

A JSON string literal with minimal escaping: the seven two-character escapes, \u00xx for remaining control characters, everything else (including multibyte text) passed through as UTF-8.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/causalontology/jcs.rb', line 36

def string(s)
  parts = +'"'
  s.each_char do |ch|
    if ESCAPES.key?(ch)
      parts << ESCAPES[ch]
    elsif ch.ord < 0x20
      parts << format("\\u%04x", ch.ord)
    else
      parts << ch
    end
  end
  parts << '"'
  parts
end