require "spec" require "json" require "spec/helpers/string" private def assert_built(expected, *, file = __FILE__, line = __LINE__, &) assert_prints JSON.build { |json| with json yield json }, expected, file: file, line: line end private class TestObject def to_json(builder) {"int" => 12}.to_json(builder) end end describe JSON::Builder do it "writes null" do assert_built("null") do null end end it "writes bool" do assert_built("true") do bool(true) end end it "writes integer" do assert_built("123") do number(123) end end it "writes float" do assert_built("123.45") do number(123.45) end end it "errors on nan" do json = JSON::Builder.new(IO::Memory.new) json.start_document expect_raises JSON::Error, "NaN not allowed in JSON" do json.number(0.0/0.0) end end it "errors on infinity" do json = JSON::Builder.new(IO::Memory.new) json.start_document expect_raises JSON::Error, "Infinity not allowed in JSON" do json.number(1.0/0.0) end end it "writes string" do assert_built(%<"hello">) do string("hello") end end it "writes string with controls and slashes " do assert_built("\" \\\" \\\\ \\b \\f \\n \\r \\t \\u0019 \"") do string(" \" \\ \b \f \n \r \t \u{19} ") end end it "errors if writing before document start" do json = JSON::Builder.new(IO::Memory.new) expect_raises JSON::Error, "Write before start_document" do json.number(1) end end it "errors if writing two scalars" do json = JSON::Builder.new(IO::Memory.new) json.start_document json.number(1) expect_raises JSON::Error, "Write past end_document and before start_document" do json.number(2) end end it "writes array" do assert_built(%<[1,"hello",true]>) do array do number(1) string("hello") bool(true) end end end it "writes nested array" do assert_built(%<[1,["hello",true],2]>) do array do number(1) array do string("hello") bool(true) end number(2) end end end it "writes object" do assert_built(%<{"foo":1,"bar":2}>) do object do string("foo") number(1) string("bar") number(2) end end end it "writes nested object" do assert_built(%<{"foo":{"bar":2,"baz":3},"another":{"baz":3}}>) do object do string("foo") object do string("bar") number(2) string("baz") number(3) end string("another") object do string("baz") number(3) end end end end it "writes array with indent level" do assert_built(%<[\n 1,\n 2,\n 3\n]>) do |json| json.indent = 2 array do number(1) number(2) number(3) end end end it "writes array with indent string" do assert_built(%<[\n\t1,\n\t2,\n\t3\n]>) do |json| json.indent = "\t" array do number(1) number(2) number(3) end end end it "writes object with indent level" do assert_built(%<{\n "foo": 1,\n "bar": 2\n}>) do |json| json.indent = 2 object do string "foo" number(1) string "bar" number(2) end end end it "writes empty array with indent level" do assert_built(%<[]>) do |json| json.indent = 2 array do end end end it "writes empty object with indent level" do assert_built(%<{}>) do |json| json.indent = 2 object do end end end it "writes nested array" do assert_built(%<[\n []\n]>) do |json| json.indent = 2 array do array do end end end end it "writes object with scalar and indent" do assert_built(%<{\n "foo": 1\n}>) do |json| json.indent = 2 object do string "foo" number 1 end end end it "writes object with array and scalar and indent" do assert_built(%<{\n "foo": [\n 1\n ]\n}>) do |json| json.indent = 2 object do string "foo" array do number 1 end end end end it "writes raw" do assert_built(%<{\n "foo": [1, 2, 3],\n "bar": [\n [4, 5, 6]\n ]\n}>) do |json| json.indent = 2 object do string "foo" raw "[1, 2, 3]" string "bar" array do raw "[4, 5, 6]" end end end end it "raises if nothing written" do json = JSON::Builder.new(IO::Memory.new) json.start_document expect_raises JSON::Error, "Empty JSON" do json.end_document end end it "raises if array is left open" do json = JSON::Builder.new(IO::Memory.new) json.start_document json.start_array expect_raises JSON::Error, "Unterminated JSON array" do json.end_document end end it "raises if object is left open" do json = JSON::Builder.new(IO::Memory.new) json.start_document json.start_object expect_raises JSON::Error, "Unterminated JSON object" do json.end_document end end it "writes field with scalar in object" do assert_built(%<{"int":42,"float":0.815,"null":null,"bool":true,"string":"string"}>) do object do field "int", 42 field "float", 0.815 field "null", nil field "bool", true field "string", "string" end end end it "writes field with arbitrary value in object" do assert_built(%<{"hash":{"hash":"value"},"object":{"int":12}}>) do object do field "hash", {"hash" => "value"} field "object", TestObject.new end end end it "errors on max nesting (array)" do io = IO::Memory.new builder = JSON::Builder.new(io) builder.max_nesting = 3 builder.start_document 3.times do builder.start_array end expect_raises(JSON::Error, "Nesting of 4 is too deep") do builder.start_array end end it "errors on max nesting (object)" do io = IO::Memory.new builder = JSON::Builder.new(io) builder.max_nesting = 3 builder.start_document 3.times do builder.start_object builder.string "key" end expect_raises(JSON::Error, "Nesting of 4 is too deep") do builder.start_object end end it "#next_is_object_key?" do io = IO::Memory.new builder = JSON::Builder.new(io) builder.next_is_object_key?.should be_false builder.start_document builder.next_is_object_key?.should be_false builder.start_object builder.next_is_object_key?.should be_true builder.scalar("foo") builder.next_is_object_key?.should be_false builder.scalar("bar") builder.next_is_object_key?.should be_true end end