Skip to content

Commit

Permalink
Allow JSON::Fragment to be used even in strict mode
Browse files Browse the repository at this point in the history
  • Loading branch information
byroot committed Jan 21, 2025
1 parent 1607273 commit 4dccf5b
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 6 deletions.
29 changes: 29 additions & 0 deletions java/src/json/ext/Generator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -115,6 +117,11 @@ private static <T extends IRubyObject> Handler<? super T> getHandlerFor(Ruby run
case HASH :
if (Helpers.metaclass(object) != runtime.getHash()) break;
return (Handler<T>) HASH_HANDLER;
case STRUCT :
RuntimeInfo info = RuntimeInfo.forRuntime(runtime);
RubyClass fragmentClass = info.jsonModule.get().getClass("Fragment");
if (Helpers.metaclass(object) != fragmentClass) break;
return (Handler<T>) FRAGMENT_HANDLER;
}
return GENERIC_HANDLER;
}
Expand Down Expand Up @@ -481,6 +488,28 @@ static RubyString ensureValidEncoding(ThreadContext context, RubyString str) {
static final Handler<IRubyObject> NIL_HANDLER =
new KeywordHandler<>("null");

/**
* The default handler (<code>Object#to_json</code>): coerces the object
* to string using <code>#to_s</code>, and serializes that string.
*/
static final Handler<IRubyObject> FRAGMENT_HANDLER =
new Handler<IRubyObject>() {
@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 (<code>Object#to_json</code>): coerces the object
* to string using <code>#to_s</code>, and serializes that string.
Expand Down
12 changes: 6 additions & 6 deletions lib/json/truffle_ruby/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions test/json/json_generator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 4dccf5b

Please sign in to comment.