diff options
-rw-r--r-- | .travis.yml | 6 | ||||
-rw-r--r-- | java/src/json/ext/Generator.java | 29 | ||||
-rwxr-xr-x | json-java.gemspec | 40 | ||||
-rw-r--r-- | json.gemspec | 12 | ||||
-rw-r--r-- | lib/json.rb | 2 | ||||
-rw-r--r-- | lib/json/common.rb | 189 | ||||
-rwxr-xr-x | tests/json_generator_test.rb | 3 |
7 files changed, 203 insertions, 78 deletions
diff --git a/.travis.yml b/.travis.yml index 920a4ac..78b19dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,14 +13,12 @@ rvm: - 2.6 - 2.7.0-preview3 - ruby-head - - jruby - - jruby-9.2.7.0 + - jruby-9.1 # Ruby 2.3 + - jruby-9.2 # Ruby 2.5 - truffleruby matrix: allow_failures: - rvm: ruby-head - - rvm: jruby - - rvm: jruby-9.2.7.0 - rvm: truffleruby script: "bundle exec rake" sudo: false diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java index cd23c73..914d00e 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -10,13 +10,10 @@ import org.jruby.RubyArray; import org.jruby.RubyBasicObject; import org.jruby.RubyBignum; import org.jruby.RubyBoolean; -import org.jruby.RubyClass; import org.jruby.RubyFixnum; import org.jruby.RubyFloat; import org.jruby.RubyHash; -import org.jruby.RubyNumeric; import org.jruby.RubyString; -import org.jruby.runtime.ClassIndex; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; @@ -32,8 +29,7 @@ public final class Generator { static <T extends IRubyObject> RubyString generateJson(ThreadContext context, T object, Handler<? super T> handler, IRubyObject[] args) { - Session session = new Session(context, args.length > 0 ? args[0] - : null); + Session session = new Session(context, args.length > 0 ? args[0] : null); return session.infect(handler.generateNew(session, object)); } @@ -43,7 +39,7 @@ public final class Generator { */ static <T extends IRubyObject> RubyString generateJson(ThreadContext context, T object, IRubyObject[] args) { - Handler<? super T> handler = getHandlerFor(context.getRuntime(), object); + Handler<? super T> handler = getHandlerFor(context.runtime, object); return generateJson(context, object, handler, args); } @@ -55,33 +51,18 @@ public final class Generator { generateJson(ThreadContext context, T object, GeneratorState config) { Session session = new Session(context, config); - Handler<? super T> handler = getHandlerFor(context.getRuntime(), object); + Handler<? super T> handler = getHandlerFor(context.runtime, object); return handler.generateNew(session, object); } - // NOTE: drop this once Ruby 1.9.3 support is gone! - private static final int FIXNUM = 1; - private static final int BIGNUM = 2; - private static final int ARRAY = 3; - private static final int STRING = 4; - private static final int NIL = 5; - private static final int TRUE = 6; - private static final int FALSE = 7; - private static final int HASH = 10; - private static final int FLOAT = 11; - // hard-coded due JRuby 1.7 compatibility - // https://github.com/jruby/jruby/blob/1.7.27/core/src/main/java/org/jruby/runtime/ClassIndex.java - /** * Returns the best serialization handler for the given object. */ // Java's generics can't handle this satisfactorily, so I'll just leave // the best I could get and ignore the warnings @SuppressWarnings("unchecked") - private static <T extends IRubyObject> - Handler<? super T> getHandlerFor(Ruby runtime, T object) { - switch (((RubyBasicObject) object).getNativeTypeIndex()) { - // can not use getNativeClassIndex due 1.7 compatibility + private static <T extends IRubyObject> Handler<? super T> getHandlerFor(Ruby runtime, T object) { + switch (((RubyBasicObject) object).getNativeClassIndex()) { case NIL : return (Handler) NIL_HANDLER; case TRUE : return (Handler) TRUE_HANDLER; case FALSE : return (Handler) FALSE_HANDLER; diff --git a/json-java.gemspec b/json-java.gemspec index 1bcd639..78cf9c9 100755 --- a/json-java.gemspec +++ b/json-java.gemspec @@ -1,33 +1,35 @@ -#!/usr/bin/env jruby -require "rubygems" +# -*- encoding: utf-8 -*- spec = Gem::Specification.new do |s| s.name = "json" s.version = File.read("VERSION").chomp - s.summary = "JSON implementation for JRuby" + + s.summary = "JSON Implementation for Ruby" s.description = "A JSON implementation as a JRuby extension." + s.licenses = ["Ruby"] s.author = "Daniel Luz" s.email = "dev+ruby@mernen.com" - s.homepage = "http://flori.github.com/json" - s.platform = 'java' s.licenses = ["Ruby"] + s.platform = 'java' + s.files = Dir["{docs,lib,tests}/**/*"] - if s.respond_to? :specification_version then - s.specification_version = 4 - - if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then - s.add_development_dependency(%q<rake>, [">= 0"]) - s.add_development_dependency(%q<test-unit>, [">= 2.0", "< 4.0"]) - else - s.add_dependency(%q<rake>, [">= 0"]) - s.add_dependency(%q<test-unit>, [">= 2.0", "< 4.0"]) - end - else - s.add_dependency(%q<rake>, [">= 0"]) - s.add_dependency(%q<test-unit>, [">= 2.0", "< 4.0"]) - end + s.homepage = "http://flori.github.com/json" + s.metadata = { + 'bug_tracker_uri' => 'https://github.com/flori/json/issues', + 'changelog_uri' => 'https://github.com/flori/json/blob/master/CHANGES.md', + 'documentation_uri' => 'http://flori.github.io/json/doc/index.html', + 'homepage_uri' => 'http://flori.github.io/json/', + 'source_code_uri' => 'https://github.com/flori/json', + 'wiki_uri' => 'https://github.com/flori/json/wiki' + } + + s.required_ruby_version = Gem::Requirement.new(">= 2.0") + s.test_files = ["tests/test_helper.rb"] + + s.add_development_dependency("rake", [">= 0"]) + s.add_development_dependency("test-unit", [">= 2.0", "< 4.0"]) end if $0 == __FILE__ diff --git a/json.gemspec b/json.gemspec index 0c72e82..8870f88 100644 --- a/json.gemspec +++ b/json.gemspec @@ -4,13 +4,15 @@ Gem::Specification.new do |s| s.name = "json" s.version = File.read('VERSION').chomp - s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.require_paths = ["lib"] - s.authors = ["Florian Frank"] + s.summary = "JSON Implementation for Ruby" s.description = "This is a JSON implementation as a Ruby extension in C." + s.licenses = ["Ruby"] + s.authors = ["Florian Frank"] s.email = "flori@ping.de" + s.extensions = ["ext/json/ext/generator/extconf.rb", "ext/json/ext/parser/extconf.rb", "ext/json/extconf.rb"] s.extra_rdoc_files = ["README.md"] + s.rdoc_options = ["--title", "JSON implemention for Ruby", "--main", "README.md"] s.files = [ ".gitignore", ".travis.yml", @@ -128,10 +130,8 @@ Gem::Specification.new do |s| 'source_code_uri' => 'https://github.com/flori/json', 'wiki_uri' => 'https://github.com/flori/json/wiki' } - s.licenses = ["Ruby"] - s.rdoc_options = ["--title", "JSON implemention for Ruby", "--main", "README.md"] + s.required_ruby_version = Gem::Requirement.new(">= 2.0") - s.summary = "JSON Implementation for Ruby" s.test_files = ["tests/test_helper.rb"] s.add_development_dependency("rake", [">= 0"]) diff --git a/lib/json.rb b/lib/json.rb index 6bb8224..aeb9774 100644 --- a/lib/json.rb +++ b/lib/json.rb @@ -319,7 +319,7 @@ require 'json/common' # opts = { # array_nl: "\n", # object_nl: "\n", -# indent+: ' ', +# indent: ' ', # space_before: ' ', # space: ' ' # } diff --git a/lib/json/common.rb b/lib/json/common.rb index ed4e047..f6230a7 100644 --- a/lib/json/common.rb +++ b/lib/json/common.rb @@ -4,13 +4,16 @@ require 'json/generic_object' module JSON class << self + # :call-seq: + # JSON[object] -> new_array or new_string + # # If +object+ is a - # {String-convertible object}[doc/implicit_conversion_rdoc.html#label-String-Convertible+Objects] - # (implementing +to_str+), calls JSON.parse with +object+ and +opts+: + # {String-convertible object}[doc/implicit_conversion_rdoc.html#label-String-Convertible+Objects], + # calls JSON.parse with +object+ and +opts+ (see method #parse): # json = '[0, 1, null]' # JSON[json]# => [0, 1, nil] # - # Otherwise, calls JSON.generate with +object+ and +opts+: + # Otherwise, calls JSON.generate with +object+ and +opts+ (see method #generate): # ruby = [0, 1, nil] # JSON[ruby] # => '[0,1,null]' def [](object, opts = {}) @@ -171,10 +174,24 @@ module JSON # For examples of parsing for all \JSON data types, see # {Parsing \JSON}[#module-JSON-label-Parsing+JSON]. # - # ====== Exceptions + # Parses nested JSON objects: + # source = <<-EOT + # { + # "name": "Dave", + # "age" :40, + # "hats": [ + # "Cattleman's", + # "Panama", + # "Tophat" + # ] + # } + # EOT + # ruby = JSON.parse(source) + # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]} # - # Raises an exception if +source+ is not valid JSON: + # --- # + # Raises an exception if +source+ is not valid JSON: # # Raises JSON::ParserError (783: unexpected token at ''): # JSON.parse('') # @@ -201,12 +218,24 @@ module JSON Parser.new(source, **(opts||{})).parse end - # Parses the content of a file (see parse method documentation for more information). + # :call-seq: + # CSV.load_file(path, opts={}) -> object + # + # Calls: + # parse(File.read(path), opts) + # + # See method #parse. def load_file(filespec, opts = {}) parse(File.read(filespec), opts) end - # Parses the content of a file (see parse! method documentation for more information). + # :call-seq: + # CSV.load_file!(path, opts = {}) + # + # Calls: + # CSV.parse!(File.read(path, opts)) + # + # See method #parse! def load_file!(filespec, opts = {}) parse!(File.read(filespec), opts) end @@ -247,8 +276,6 @@ module JSON # # Raises an exception if any formatting option is not a \String. # - # ====== Exceptions - # # Raises an exception if +obj+ contains circular references: # a = []; b = []; a.push(b); b.push(a) # # Raises JSON::NestingError (nesting of 100 is too deep): @@ -280,6 +307,9 @@ module JSON module_function :unparse # :startdoc: + # :call-seq: + # JSON.fast_generate(obj, opts) -> new_string + # # Arguments +obj+ and +opts+ here are the same as # arguments +obj+ and +opts+ in JSON.generate. # @@ -384,20 +414,135 @@ module JSON :create_additions => true, } - # Load a ruby data structure from a JSON _source_ and return it. A source can - # either be a string-like object, an IO-like object, or an object responding - # to the read method. If _proc_ was given, it will be called with any nested - # Ruby object as an argument recursively in depth first order. To modify the - # default options pass in the optional _options_ argument as well. + # :call-seq: + # JSON.load(source, proc = nil, options = {}) -> object # - # BEWARE: This method is meant to serialise data from trusted user input, - # like from your own database server or clients under your control, it could - # be dangerous to allow untrusted users to pass JSON sources into it. The - # default options for the parser can be changed via the load_default_options - # method. + # Returns the Ruby objects created by parsing the given +source+. + # + # - Argument +source+ must be, or be convertible to, a \String: + # - If +source+ responds to instance method +to_str+, + # <tt>source.to_str</tt> becomes the source. + # - If +source+ responds to instance method +to_io+, + # <tt>source.to_io.read</tt> becomes the source. + # - If +source+ responds to instance method +read+, + # <tt>source.read</tt> becomes the source. + # - If both of the following are true, source becomes the \String <tt>'null'</tt>: + # - Option +allow_blank+ specifies a truthy value. + # - The source, as defined above, is +nil+ or the empty \String <tt>''</tt>. + # - Otherwise, +source+ remains the source. + # - Argument +proc+, if given, must be a \Proc that accepts one argument. + # It will be called recursively with each result (depth-first order). + # See details below. + # BEWARE: This method is meant to serialise data from trusted user input, + # like from your own database server or clients under your control, it could + # be dangerous to allow untrusted users to pass JSON sources into it. + # - Argument +opts+, if given, contains options for the parsing, and must be a + # {Hash-convertible object}[doc/implicit_conversion_rdoc.html#label-Hash+Convertible+Objects]. + # See {Parsing Options}[#module-JSON-label-Parsing+Options]. + # The default options can be changed via method JSON.load_default_options=. + # + # --- + # + # When no +proc+ is given, modifies +source+ as above and returns the result of + # <tt>parse(source, opts)</tt>; see #parse. + # + # Source for following examples: + # source = <<-EOT + # { + # "name": "Dave", + # "age" :40, + # "hats": [ + # "Cattleman's", + # "Panama", + # "Tophat" + # ] + # } + # EOT + # + # Load a \String: + # ruby = JSON.load(source) + # ruby # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]} + # + # Load an \IO object: + # require 'stringio' + # object = JSON.load(StringIO.new(source)) + # object # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]} + # + # Load a \File object: + # path = 't.json' + # File.write(path, source) + # File.open(path) do |file| + # JSON.load(file) + # end # => {"name"=>"Dave", "age"=>40, "hats"=>["Cattleman's", "Panama", "Tophat"]} + # + # --- + # + # When +proc+ is given: + # - Modifies +source+ as above. + # - Gets the +result+ from calling <tt>parse(source, opts)</tt>. + # - Recursively calls <tt>proc(result)</tt>. + # - Returns the final result. + # + # Example: + # require 'json' + # + # # Some classes for the example. + # class Base + # def initialize(attributes) + # @attributes = attributes + # end + # end + # class User < Base; end + # class Account < Base; end + # class Admin < Base; end + # # The JSON source. + # json = <<-EOF + # { + # "users": [ + # {"type": "User", "username": "jane", "email": "jane@example.com"}, + # {"type": "User", "username": "john", "email": "john@example.com"} + # ], + # "accounts": [ + # {"account": {"type": "Account", "paid": true, "account_id": "1234"}}, + # {"account": {"type": "Account", "paid": false, "account_id": "1235"}} + # ], + # "admins": {"type": "Admin", "password": "0wn3d"} + # } + # EOF + # # Deserializer method. + # def deserialize_obj(obj, safe_types = %w(User Account Admin)) + # type = obj.is_a?(Hash) && obj["type"] + # safe_types.include?(type) ? Object.const_get(type).new(obj) : obj + # end + # # Call to JSON.load + # ruby = JSON.load(json, proc {|obj| + # case obj + # when Hash + # obj.each {|k, v| obj[k] = deserialize_obj v } + # when Array + # obj.map! {|v| deserialize_obj v } + # end + # }) + # pp ruby + # Output: + # {"users"=> + # [#<User:0x00000000064c4c98 + # @attributes= + # {"type"=>"User", "username"=>"jane", "email"=>"jane@example.com"}>, + # #<User:0x00000000064c4bd0 + # @attributes= + # {"type"=>"User", "username"=>"john", "email"=>"john@example.com"}>], + # "accounts"=> + # [{"account"=> + # #<Account:0x00000000064c4928 + # @attributes={"type"=>"Account", "paid"=>true, "account_id"=>"1234"}>}, + # {"account"=> + # #<Account:0x00000000064c4680 + # @attributes={"type"=>"Account", "paid"=>false, "account_id"=>"1235"}>}], + # "admins"=> + # #<Admin:0x00000000064c41f8 + # @attributes={"type"=>"Admin", "password"=>"0wn3d"}>} # - # This method is part of the implementation of the load/dump interface of - # Marshal and YAML. def load(source, proc = nil, options = {}) opts = load_default_options.merge options if source.respond_to? :to_str @@ -416,7 +561,7 @@ module JSON end # Recursively calls passed _Proc_ if the parsed data structure is an _Array_ or _Hash_ - def recurse_proc(result, &proc) + def recurse_proc(result, &proc) # :nodoc: case result when Array result.each { |x| recurse_proc x, &proc } diff --git a/tests/json_generator_test.rb b/tests/json_generator_test.rb index 13d3b5a..77b539d 100755 --- a/tests/json_generator_test.rb +++ b/tests/json_generator_test.rb @@ -49,7 +49,6 @@ EOT end def test_remove_const_segv - return if RUBY_ENGINE == 'jruby' stress = GC.stress const = JSON::SAFE_STATE_PROTOTYPE.dup @@ -76,7 +75,7 @@ EOT silence do JSON.const_set :SAFE_STATE_PROTOTYPE, const end - end if JSON.const_defined?("Ext") + end if JSON.const_defined?("Ext") && RUBY_ENGINE != 'jruby' def test_generate json = generate(@hash) |