diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java index 66986927..b67c0508 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -5,6 +5,8 @@ */ package json.ext; +import json.ext.RuntimeInfo; + import org.jcodings.Encoding; import org.jcodings.specific.ASCIIEncoding; import org.jcodings.specific.USASCIIEncoding; @@ -115,6 +117,11 @@ private static Handler getHandlerFor(Ruby run case HASH : if (Helpers.metaclass(object) != runtime.getHash()) break; return (Handler) HASH_HANDLER; + case STRUCT : + RuntimeInfo info = RuntimeInfo.forRuntime(runtime); + RubyClass fragmentClass = info.jsonModule.get().getClass("Fragment"); + if (Helpers.metaclass(object) != fragmentClass) break; + return (Handler) FRAGMENT_HANDLER; } return GENERIC_HANDLER; } @@ -481,6 +488,28 @@ static RubyString ensureValidEncoding(ThreadContext context, RubyString str) { static final Handler NIL_HANDLER = new KeywordHandler<>("null"); + /** + * The default handler (Object#to_json): coerces the object + * to string using #to_s, and serializes that string. + */ + static final Handler FRAGMENT_HANDLER = + new Handler() { + @Override + RubyString generateNew(ThreadContext context, Session session, IRubyObject object) { + GeneratorState state = session.getState(context); + IRubyObject result = object.callMethod(context, "to_json", state); + if (result instanceof RubyString) return (RubyString)result; + throw context.runtime.newTypeError("to_json must return a String"); + } + + @Override + void generate(ThreadContext context, Session session, IRubyObject object, OutputStream buffer) throws IOException { + RubyString result = generateNew(context, session, object); + ByteList bytes = result.getByteList(); + buffer.write(bytes.unsafeBytes(), bytes.begin(), bytes.length()); + } + }; + /** * The default handler (Object#to_json): coerces the object * to string using #to_s, and serializes that string. diff --git a/lib/json/truffle_ruby/generator.rb b/lib/json/truffle_ruby/generator.rb index 59e8f651..f70e3303 100644 --- a/lib/json/truffle_ruby/generator.rb +++ b/lib/json/truffle_ruby/generator.rb @@ -412,10 +412,10 @@ def to_json(state = nil, *) state = State.from_state(state) if state if state&.strict? value = self - if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value) + if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value) if state.as_json value = state.as_json.call(value) - unless false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value + unless false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value raise GeneratorError.new("#{value.class} returned by #{state.as_json} not allowed in JSON", value) end value.to_json(state) @@ -472,10 +472,10 @@ def json_transform(state) end result = +"#{result}#{key_json}#{state.space_before}:#{state.space}" - if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value) + if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value) if state.as_json value = state.as_json.call(value) - unless false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value + unless false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value raise GeneratorError.new("#{value.class} returned by #{state.as_json} not allowed in JSON", value) end result << value.to_json(state) @@ -533,10 +533,10 @@ def json_transform(state) each { |value| result << delim unless first result << state.indent * depth if indent - if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value) + if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value) if state.as_json value = state.as_json.call(value) - unless false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value + unless false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value raise GeneratorError.new("#{value.class} returned by #{state.as_json} not allowed in JSON", value) end result << value.to_json(state) diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 92115637..7eb95c62 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -668,6 +668,7 @@ def test_nonutf8_encoding def test_fragment fragment = JSON::Fragment.new(" 42") assert_equal '{"number": 42}', JSON.generate({ number: fragment }) + assert_equal '{"number": 42}', JSON.generate({ number: fragment }, strict: true) end def test_json_generate_as_json_convert_to_proc