diff --git a/Mavenfile b/Mavenfile index 4c5b601..a189380 100644 --- a/Mavenfile +++ b/Mavenfile @@ -10,16 +10,15 @@ packaging :jar properties 'project.build.sourceEncoding' => 'UTF-8', # create a pom.xml from this here - 'polyglot.dump.pom' => 'pom.xml' + 'polyglot.dump.pom' => 'pom.xml', + 'maven.compiler.release' => '11' jar 'junit:junit', '4.13.2', :scope => :test -jar 'org.jruby:jruby', '9.3.14.0', :scope => :provided +jar 'org.jruby:jruby', '9.4.14.0', :scope => :provided -plugin :compiler, '3.1', :source => '1.8', :target => '1.8', +plugin :compiler, '3.11.0', :release => '11', :showDeprecation => false, - :showWarnings => false, - :executable => '${JAVA_HOME}/bin/javac', - :fork => true + :showWarnings => false -plugin :surefire, '2.17', :skipTests => true +plugin :surefire, '2.17', :skipTests => false diff --git a/benchmarking/sourced.rb b/benchmarking/sourced.rb deleted file mode 100644 index f88f7bb..0000000 --- a/benchmarking/sourced.rb +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env ruby - -# require 'rubygems' -# require 'bigdecimal' -# require 'benchmark' -# require 'thread' -require 'digest' - -$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) -require 'jrjackson' - -HASH = {:one => nil, :two => nil, :three => nil, :four => {:a => nil, :b => nil, :c =>nil}, -:five => {:d => nil, :e => nil}, -:six => {:f => nil, :g => nil, :h =>[], :i => nil, :j => nil, :k => nil, :l => nil}, -:seven => nil, :eight => [], -:nine => {:m => {:A => nil, :B => nil}}} - -def random_string - Digest::MD5.hexdigest "#{Time.now + rand(999)}" -end - -def random_number - rand 999_999_999 -end - -def random_float - random_number + rand -end - -def fill_array - value = [] - 5.times do - value.push send(METHODS[rand(3)]) - end - value -end - -def randomize_entries hsh - new_hsh = {} - hsh.each_pair do |key, value| - case value - when NilClass - new_hsh[key] = send METHODS[rand(3)] - when Hash - new_hsh[key] = randomize_entries value - when Array - new_hsh[key] = fill_array - end - end - new_hsh -end - -METHODS = [:random_string, :random_number, :random_float] - -array_size = 100000 - -filename = File.expand_path('benchmarking/fixtures/source4.json') - -array = Array.new(array_size) - -File.open(filename, 'w') do |f| - f.write( JrJackson::Json.dump( - array.map{|e| randomize_entries(HASH.dup)} - ) - ) -end diff --git a/changelog.md b/changelog.md index 11d5692..4989519 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,7 @@ -v0.4.21 +v0.5.0 + Upgrade jackson and jackson-databind to v2.21.2 + Require JRuby 9.4.14.0 and Java 11 toolchain for builds + Drop legacy Gson benchmarks and dependency Add bigdecimal as a runtime dependency for JRuby 10 / Ruby 3.4+ compatibility Add GitHub Actions CI workflow diff --git a/jrjackson.gemspec b/jrjackson.gemspec index 7f3485e..ca809cc 100644 --- a/jrjackson.gemspec +++ b/jrjackson.gemspec @@ -22,9 +22,10 @@ Gem::Specification.new do |s| s.add_development_dependency 'jar-dependencies', '>= 0.3.2' jackson_version = JrJackson::BuildInfo.jackson_version + jackson_annotations_version = JrJackson::BuildInfo.jackson_annotations_version jackson_databind_version = JrJackson::BuildInfo.jackson_databind_version s.requirements << "jar com.fasterxml.jackson.core:jackson-core, #{jackson_version}" - s.requirements << "jar com.fasterxml.jackson.core:jackson-annotations, #{jackson_version}" + s.requirements << "jar com.fasterxml.jackson.core:jackson-annotations, #{jackson_annotations_version}" s.requirements << "jar com.fasterxml.jackson.core:jackson-databind, #{jackson_databind_version}" s.requirements << "jar com.fasterxml.jackson.module:jackson-module-afterburner, #{jackson_version}" diff --git a/lib/jrjackson/build_info.rb b/lib/jrjackson/build_info.rb index e674009..5bfdf69 100644 --- a/lib/jrjackson/build_info.rb +++ b/lib/jrjackson/build_info.rb @@ -1,11 +1,11 @@ module JrJackson module BuildInfo def self.version - '0.4.21' + '0.5.0' end def self.release_date - '2026-03-16' + '2026-04-17' end def self.files @@ -13,15 +13,19 @@ def self.files end def self.jackson_version - '2.15.4' + '2.21.2' + end + + def self.jackson_annotations_version + '2.21' end def self.jackson_databind_version - '2.15.4' + '2.21.2' end def self.jar_version - '1.2.37' + '1.3.0' end private @@ -36,7 +40,7 @@ def self.repo_files def self.generated_jar_files [ - "lib/com/fasterxml/jackson/core/jackson-annotations/#{jackson_version}/jackson-annotations-#{jackson_version}.jar", + "lib/com/fasterxml/jackson/core/jackson-annotations/#{jackson_annotations_version}/jackson-annotations-#{jackson_annotations_version}.jar", "lib/com/fasterxml/jackson/core/jackson-core/#{jackson_version}/jackson-core-#{jackson_version}.jar", "lib/com/fasterxml/jackson/core/jackson-databind/#{jackson_databind_version}/jackson-databind-#{jackson_databind_version}.jar", "lib/com/fasterxml/jackson/module/jackson-module-afterburner/#{jackson_version}/jackson-module-afterburner-#{jackson_version}.jar", diff --git a/run_all_individual_bench.sh b/run_all_individual_bench.sh index 3244941..82ba4f9 100755 --- a/run_all_individual_bench.sh +++ b/run_all_individual_bench.sh @@ -3,21 +3,21 @@ chruby-exec ruby-2.2.2 -- ruby benchmarking/individual/json-mri-sourced.rb chruby-exec ruby-2.2.2 -- ruby benchmarking/individual/oj-mri-sourced.rb -chruby-exec jruby-9.2.0.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/json-jr-sourced.rb +chruby-exec jruby-9.4.14.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/json-jr-sourced.rb -chruby-exec jruby-9.2.0.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/string-jr-sourced.rb -chruby-exec jruby-9.2.0.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/str-bd-jr-sourced.rb +chruby-exec jruby-9.4.14.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/string-jr-sourced.rb +chruby-exec jruby-9.4.14.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/str-bd-jr-sourced.rb -chruby-exec jruby-9.2.0.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/symbol-jr-sourced.rb -chruby-exec jruby-9.2.0.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/sym-bd-jr-sourced.rb +chruby-exec jruby-9.4.14.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/symbol-jr-sourced.rb +chruby-exec jruby-9.4.14.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/sym-bd-jr-sourced.rb -chruby-exec jruby-9.2.0.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/raw-jr-sourced.rb -chruby-exec jruby-9.2.0.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/raw-bd-jr-sourced.rb +chruby-exec jruby-9.4.14.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/raw-jr-sourced.rb +chruby-exec jruby-9.4.14.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/raw-bd-jr-sourced.rb -chruby-exec jruby-9.2.0.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/sj-jr-sourced.rb +chruby-exec jruby-9.4.14.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/sj-jr-sourced.rb chruby-exec ruby-2.2.2 -- ruby benchmarking/individual/json-gen-mri-sourced.rb chruby-exec ruby-2.2.2 -- ruby benchmarking/individual/oj-gen-mri-sourced.rb -chruby-exec jruby-9.2.0.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/json-gen-jr-sourced.rb -chruby-exec jruby-9.2.0.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/raw-gen-jr-sourced.rb +chruby-exec jruby-9.4.14.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/json-gen-jr-sourced.rb +chruby-exec jruby-9.4.14.0 -- ruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m benchmarking/individual/raw-gen-jr-sourced.rb diff --git a/run_jruby_individual_bench.sh b/run_jruby_individual_bench.sh index 4dfab5a..233dd2b 100755 --- a/run_jruby_individual_bench.sh +++ b/run_jruby_individual_bench.sh @@ -15,4 +15,3 @@ jruby -J-Xmn1024m -J-Xms4096m -J-Xmx4096m -S ./benchmarking/individual/raw-jr-so jruby -J-Xmn1024m -J-Xms4096m -J-Xmx4096m -S ./benchmarking/individual/raw-bd-jr-sourced.rb jruby -J-Xmn1024m -J-Xms4096m -J-Xmx4096m -S ./benchmarking/individual/sj-jr-sourced.rb - diff --git a/src/main/java/com/jrjackson/JrJacksonBase.java b/src/main/java/com/jrjackson/JrJacksonBase.java index 5b1748e..18e665c 100644 --- a/src/main/java/com/jrjackson/JrJacksonBase.java +++ b/src/main/java/com/jrjackson/JrJacksonBase.java @@ -22,7 +22,6 @@ import org.jruby.RubySymbol; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; -import org.jruby.ext.stringio.StringIO; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; @@ -109,16 +108,26 @@ protected static IRubyObject _sjcparse(ThreadContext context, IRubyObject handle return sp.deserialize(jp); } - protected static JsonParser buildParser(ThreadContext ctx, JsonFactory jf, IRubyObject arg) throws IOException { + private static boolean isStringIO(IRubyObject arg) { + return "StringIO".equals(arg.getMetaClass().getName()); + } + + protected static byte[] extractBytes(ThreadContext ctx, IRubyObject arg) { if (arg instanceof RubyString) { - return jf.createParser(((RubyString) arg).getByteList().bytes()); - } else if (arg instanceof StringIO) { - RubyString content = (RubyString) ((StringIO) arg).string(ctx); - return jf.createParser(content.getByteList().bytes()); - } else { - // must be an IO object then - return jf.createParser(((RubyIO) arg).getInStream()); + return ((RubyString) arg).getByteList().bytes(); + } else if (isStringIO(arg)) { + RubyString content = arg.callMethod(ctx, "string").convertToString(); + return content.getByteList().bytes(); + } + return null; + } + + protected static JsonParser buildParser(ThreadContext ctx, JsonFactory jf, IRubyObject arg) throws IOException { + byte[] bytes = extractBytes(ctx, arg); + if (bytes != null) { + return jf.createParser(bytes); } + return jf.createParser(((RubyIO) arg).getInStream()); } protected static IRubyObject _parse(ThreadContext context, IRubyObject arg, ObjectMapper mapper) throws IOException, RaiseException { @@ -128,13 +137,10 @@ protected static IRubyObject _parse(ThreadContext context, IRubyObject arg, Obje mapper.setDateFormat(simpleFormat); try { Object o; - if (arg instanceof RubyString) { - o = mapper.readValue(((RubyString) arg).getByteList().bytes(), Object.class); - } else if (arg instanceof StringIO) { - RubyString content = (RubyString) ((StringIO) arg).string(context); - o = mapper.readValue(content.getByteList().bytes(), Object.class); + byte[] bytes = extractBytes(context, arg); + if (bytes != null) { + o = mapper.readValue(bytes, Object.class); } else { - // must be an IO object then o = mapper.readValue(((RubyIO) arg).getInStream(), Object.class); } return RubyUtils.rubyObject(ruby, o); diff --git a/src/main/java/com/jrjackson/RubyAnySerializer.java b/src/main/java/com/jrjackson/RubyAnySerializer.java index 5ab06e8..861f8b0 100644 --- a/src/main/java/com/jrjackson/RubyAnySerializer.java +++ b/src/main/java/com/jrjackson/RubyAnySerializer.java @@ -4,6 +4,8 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.type.WritableTypeId; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; import org.jruby.RubyArray; import org.jruby.RubyClass; @@ -255,22 +257,18 @@ private void serializeKey(IRubyObject key, JsonGenerator jgen, SerializerProvide } } - /** - * Default implementation will write type prefix, call regular serialization method (since assumption is that value itself does not need JSON Array or Object start/end markers), and then write type suffix. This should work for most cases; some sub-classes may want to change this behavior. - * - * @param value - * @param jgen - * @param provider - * @param typeSer - * @throws java.io.IOException - * @throws com.fasterxml.jackson.core.JsonGenerationException - */ + // TODO: Remove this method. It is dead code that is never triggered because: + // 1. JrJacksonBase.generate() calls serialize() directly, bypassing ObjectMapper.writeValue() + // 2. No default typing or @JsonTypeInfo is configured on the ObjectMapper + // Kept for now in case external consumers use the ObjectMapper with polymorphic typing. + @Deprecated(since = "0.5.0", forRemoval = true) @Override public void serializeWithType(IRubyObject value, JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) throws IOException, JsonGenerationException { - typeSer.writeTypePrefixForScalar(value, jgen); + WritableTypeId typeId = typeSer.typeId(value, JsonToken.VALUE_STRING); + typeSer.writeTypePrefix(jgen, typeId); serialize(value, jgen, provider); - typeSer.writeTypeSuffixForScalar(value, jgen); + typeSer.writeTypeSuffix(jgen, typeId); } enum RUBYCLASS { diff --git a/src/main/java/com/jrjackson/RubyJacksonModule.java b/src/main/java/com/jrjackson/RubyJacksonModule.java index 16d041c..c95361a 100644 --- a/src/main/java/com/jrjackson/RubyJacksonModule.java +++ b/src/main/java/com/jrjackson/RubyJacksonModule.java @@ -38,7 +38,7 @@ protected SimpleDateFormat initialValue() { } private RubyJacksonModule() { - super("JrJacksonStrModule", new Version(1, 2, 28, "0", "com.jrjackson.jruby", "jrjackson")); + super("JrJacksonStrModule", new Version(1, 3, 0, "0", "com.jrjackson.jruby", "jrjackson")); } public static ObjectMapper mapperWith(Ruby ruby, RubyKeyConverter nameConverter, diff --git a/src/main/java/com/jrjackson/RubyObjectDeserializer.java b/src/main/java/com/jrjackson/RubyObjectDeserializer.java index da786bb..8dd6586 100644 --- a/src/main/java/com/jrjackson/RubyObjectDeserializer.java +++ b/src/main/java/com/jrjackson/RubyObjectDeserializer.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.util.ObjectBuffer; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; @@ -90,7 +91,13 @@ public RubyObject deserialize(JsonParser jp, DeserializationContext ctxt) case END_ARRAY: // invalid case END_OBJECT: // invalid default: - throw ctxt.mappingException(Object.class); + throw JsonMappingException.from( + jp, + String.format( + "Unexpected token (%s) when deserializing Ruby object", + jp.currentToken() + ) + ); } } diff --git a/src/test/java/com/jrjackson/RubyAnySerializerTest.java b/src/test/java/com/jrjackson/RubyAnySerializerTest.java index 47dc47f..2863c25 100644 --- a/src/test/java/com/jrjackson/RubyAnySerializerTest.java +++ b/src/test/java/com/jrjackson/RubyAnySerializerTest.java @@ -4,10 +4,8 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; import org.jcodings.specific.UTF8Encoding; -import org.jruby.CompatVersion; import org.jruby.Ruby; import org.jruby.RubyHash; -import org.jruby.RubyInstanceConfig; import org.jruby.ext.bigdecimal.RubyBigDecimal; import org.jruby.util.ByteList; import org.junit.Before; @@ -27,15 +25,11 @@ public class RubyAnySerializerTest { public void setUp() throws Exception { if (setupDone) return; - RubyInstanceConfig config_19 = new RubyInstanceConfig(); - config_19.setCompatVersion(CompatVersion.RUBY1_9); - ruby = Ruby.newInstance(config_19); + ruby = Ruby.newInstance(); RubyBigDecimal.createBigDecimal(ruby); // we need to do 'require "bigdecimal"' -// JrubyTimestampExtLibrary.createTimestamp(ruby); setupDone = true; } - @Test public void testSerialize() throws Exception { RubyHash rh = RubyHash.newHash(ruby); @@ -53,4 +47,4 @@ public void testSerialize() throws Exception { UTF8Encoding.INSTANCE); assertThat(bl.toString(), is(equalTo("{\"somekey\":123}"))); } -} \ No newline at end of file +} diff --git a/test/jrjackson_test.rb b/test/jrjackson_test.rb index 408ccdd..549f227 100755 --- a/test/jrjackson_test.rb +++ b/test/jrjackson_test.rb @@ -478,6 +478,33 @@ def test_bad_utf end end + def test_deserializer_unexpected_token_error_path + require 'java' + + runtime = org.jruby.Ruby.getGlobalRuntime + deserializer = Java::ComJrjackson::RubyObjectDeserializer.new + deserializer = deserializer.with( + runtime, + Java::ComJrjackson::RubyStringKeyConverter.new, + Java::ComJrjackson::RubyIntValueConverter.new, + Java::ComJrjackson::RubyFloatValueConverter.new + ) + + mapper = Java::ComFasterxmlJacksonDatabind::ObjectMapper.new + parser = mapper.factory.create_parser("[]") + + parser.next_token # START_ARRAY + parser.next_token # END_ARRAY + + context = mapper.deserialization_context + + error = assert_raise(Java::ComFasterxmlJacksonDatabind::JsonMappingException) do + deserializer.deserialize(parser, context) + end + + assert_match(/Unexpected token \(END_ARRAY\) when deserializing Ruby object/, error.message) + end + def test_can_parse_bignum expected = 12345678901234567890123456789 json = '{"foo":12345678901234567890123456789}'