INDENT_SIZE = 2

def pretty_jsonify(struct, indent=0)
  case struct
  when Array
    "[\n" + indent_str("", indent+INDENT_SIZE) +

    struct.map { |value|
      pretty_jsonify(value, indent+INDENT_SIZE)
    }.join(",\n#{indent_str('', indent+INDENT_SIZE)}") +

    "\n" + indent_str("]", indent)
  when Hash
   "{\n" + indent_str("", indent+INDENT_SIZE) +

    struct.map { |key, value|
      pretty_jsonify(key, indent+INDENT_SIZE) + ": " + pretty_jsonify(value, indent+INDENT_SIZE)
    }.join(",\n#{indent_str('', indent+INDENT_SIZE)}") +

    "\n" + indent_str("}", indent)
  when nil
    "null"
  when Numeric
    "#{struct}"
  when TrueClass
    "true"
  when FalseClass
    "false"
  else
    "\"#{struct}\""
  end
end

def indent_str(str, indent)
  " "*indent + str
end

cases = [
    {
      i: 'foo',
      o: '"foo"'
    },
    {
      i: 1,
      o: '1'
    },
    {
      i: 1.5,
      o: '1.5'
    },
    {
      i: true,
      o: 'true'
    },
    {
      i: nil,
      o: 'null'
    },
    {
      i: { foo: 'bar', baz: 'buzz' },
      o: %Q#{\n  "foo": "bar",\n  "baz": "buzz"\n}#
    },
    {
      i: ['a', 'b', 'c'],
      o: %Q#[\n  "a",\n  "b",\n  "c"\n]#
    },
    {
      i: { foo: 'bar', baz: { fizz: 'buzz', waz: 1 }, wiz: [ 2, { fuzz: true }, nil, [ 'a', 'b' ] ] },
      o: %Q{{\n  "foo": "bar",\n  "baz": {\n    "fizz": "buzz",\n    "waz": 1\n  },\n  "wiz": [\n    2,\n    {\n      "fuzz": true\n    },\n    null,\n    [\n      "a",\n      "b"\n    ]\n  ]\n}}
    }
]

cases.each_with_index { |c, idx|
  if pretty_jsonify(c[:i]) == c[:o]
    puts "Test case #{idx} PASSED:\n#{c[:o]}"
  else
    puts "Test case #{idx} FAILED:\n#{pretty_jsonify(c[:i])}\nvs\n#{c[:o]}"
  end
}