WoW! I finally found a way to show the differentiation in speed between JSON::Any and static types

I went ahead and did a benchmark with 4 structs using JSON::Serializable, each with some primitive data types and a nested object.

require "benchmark"
require "json"

struct CorsObject
  include JSON::Serializable

  getter str : String
  getter int : Int32
  getter int_64 : Int64
  getter float : Float64
  getter bool : Bool
end

struct CorsConfig
  include JSON::Serializable

  getter cors_object : CorsObject

  getter str : String
  getter int : Int32
  getter int_64 : Int64
  getter float : Float64
  getter bool : Bool
end

struct RouteConfig
  include JSON::Serializable

  getter cors : CorsConfig

  getter str : String
  getter int : Int32
  getter int_64 : Int64
  getter float : Float64
  getter bool : Bool
end

struct Config
  include JSON::Serializable

  getter routing : RouteConfig

  getter str : String
  getter int : Int32
  getter int_64 : Int64
  getter float : Float64
  getter bool : Bool
end

json_str = <<-JSON
{
  "str": "config_string",
  "int": 1,
  "int_64": 111,
  "float": 1.11,
  "bool": true,
  "routing": {
    "str": "routing_string",
    "int": 2,
    "int_64": 222,
    "float": 2.22,
    "bool": false,
    "cors": {
      "str": "cors_string",
      "int": 3,
      "int_64": 3,
      "float": 3.33,
      "bool": false,
      "cors_object": {
        "str": "cors_object_string",
        "int": 4,
        "int_64": 444,
        "float": 4.44,
        "bool": true
      }
    }
  }
}
JSON

json_config = Config.from_json json_str
json_parse_config = JSON.parse json_str

puts "Just parsing the structure"
Benchmark.ips do |x|
  x.report("from_json") do
    Config.from_json json_str
  end
  x.report("JSON.parse") do
    JSON.parse json_str
  end
end

puts

puts "Parse the structure and read a nested value"
Benchmark.ips do |x|
  x.report("from_json") do
    config = Config.from_json json_str
    config.routing.cors.float
  end
  x.report("JSON.parse") do
    config = JSON.parse json_str
    config["routing"]["cors"]["float"].as_f
  end
end

puts

puts "Just access already parsed data"
Benchmark.ips do |x|
  x.report("from_json") do
    json_config.routing.cors.float
  end
  x.report("JSON.parse") do
    json_parse_config["routing"]["cors"]["float"].as_f
  end
end

The results

Just parsing the structure
from_json 309.73k ( 3.23µs) (± 2.96%) 1728 B/op fastest
JSON.parse 264.19k ( 3.79µs) (± 2.65%) 3697 B/op 1.17× slower

Parse the structure and read a nested value
from_json 306.01k ( 3.27µs) (± 4.53%) 1728 B/op fastest
JSON.parse 259.74k ( 3.85µs) (± 2.80%) 3697 B/op 1.18× slower

Just access already parsed data
from_json 884.2M ( 1.13ns) (± 2.47%) 0 B/op fastest
JSON.parse 23.16M ( 43.17ns) (± 1.96%) 0 B/op 38.17× slower

So from this, what can we tell?

  1. from_json is slightly faster in parsing the string into an object.
  2. from_json is slightly faster in parsing the string, then reading a value from it
  3. from_json is substantially faster in reading values after the initial parsing.

In conclusion, from_json is faster in every way. JSON.parse comes close in initial parsing of the string, probably due to them both using similar parsing methods. However, once the string is parsed into an object, in this case structs, reading values from it is much faster.