oj 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of oj might be problematic. Click here for more details.

data/README.md CHANGED
@@ -22,13 +22,9 @@ A fast JSON parser and Object marshaller as a Ruby gem.
22
22
 
23
23
  ## <a name="release">Release Notes</a>
24
24
 
25
- ### Release 0.7.0
25
+ ### Release 0.8.0
26
26
 
27
- - changed the object JSON format
28
-
29
- - serialized Ruby Objects can now be deserialized
30
-
31
- - improved performance testing
27
+ - Auto creation of data classes when unmarshalling Objects if the Class is not defined
32
28
 
33
29
  ## <a name="description">Description</a>
34
30
 
@@ -57,11 +53,13 @@ to_hash() is more flexible and produces more consistent output so it has a
57
53
  preference over the to_json() method. If neither the to_json() or to_hash()
58
54
  methods exist then the Oj internal Object variable encoding is used.
59
55
 
60
- Coming soon: As an Object marshaller with support for circular references.
56
+ Oj is compatible with Ruby 1.8.7, 1.9.2, 1.9.3, JRuby, and RBX.
61
57
 
62
- Coming soon: A JSON stream parser.
58
+ ## <a name="plans">Planned Releases</a>
63
59
 
64
- Oj is compatible with Ruby 1.8.7, 1.9.2, 1.9.3, JRuby, and RBX.
60
+ - Release 0.9: Support for circular references.
61
+
62
+ - Release 1.0: A JSON stream parser.
65
63
 
66
64
  ## <a name="compare">Comparisons</a>
67
65
 
@@ -81,6 +79,9 @@ Object encoding and without Bignums.
81
79
  None of the packages except Oj were able to serialize Ruby Objects that did
82
80
  not have a to_json() method or were of the 7 native JSON types.
83
81
 
82
+ A perf_obj.rb file was added for comparing different Object marshalling
83
+ packages.
84
+
84
85
  It is also worth noting that although Oj is slightly behind MessagePack for
85
86
  parsing, Oj serialization is much faster than MessagePack even though Oj uses
86
87
  human readable JSON vs the binary MessagePack format.
@@ -227,3 +228,50 @@ without Objects or numbers (for JSON Pure) JSON:
227
228
  h2 = Oj.parse(json)
228
229
  puts "Same? #{h == h2}"
229
230
  # true
231
+
232
+ ### Object JSON format:
233
+
234
+ In :object mode Oj generates JSON that follows conventions which allow Class
235
+ and other information such as Object IDs for circular reference detection. The
236
+ formating follows the following rules.
237
+
238
+ 1. JSON native types, true, false, nil, String, Hash, Array, and Number are
239
+ encoded normally.
240
+
241
+ 2. If a Hash uses Symbols as keys those keys appear as Strings with a leading
242
+ ':' character.
243
+
244
+ 3. The '^' character denotes a special key value when in a JSON Object sequence.
245
+
246
+ 4. If a String begins with a ':' character such as ':abc' it is encoded as {"^s":":abc"}.
247
+
248
+ 5. If a Symbol begins with a ':' character such as :":abc" is is encoded as {"^m":":abc"}.
249
+
250
+ 6. A "^c" JSON Object key indicates the value should be converted to a Ruby
251
+ class. The sequence {"^c":"Oj::Bag"} is read as the Oj::Bag class.
252
+
253
+ 7. A "^t" JSON Object key indicates the value should be converted to a Ruby
254
+ Time. The sequence {"^t":1325775487.000000} is read as Jan 5, 2012 at
255
+ 23:58:07.
256
+
257
+ 8. A "^o" JSON Object key indicates the value should be converted to a Ruby
258
+ Object. The first entry in the JSON Object must be a class with the "^o"
259
+ key. After that each entry is treated as a variable of the Object where the
260
+ key is the variable name without the preceeding '@'. An example is
261
+ {"^o":"Oj::Bag","x":58,"y":"marbles"}.
262
+
263
+ 9. When encoding an Object, if the variable name does not begin with an '@'
264
+ character then the name preceeded by a '~' character. This occurs in the
265
+ Exception class. An example is {"^o":"StandardError","~mesg":"A
266
+ Message","~bt":[".\/tests.rb:345:in `test_exception'"]}
267
+
268
+ 10. If a Hash entry has a key that is not a String or Symbol then the entry is
269
+ encoded with a key of the form "^#n" where n is a hex number. The value that
270
+ is an Array where the first element is the key in the Hash and the second is
271
+ the value. An example is {"^#3":[2,5]}.
272
+
273
+ 11. A "^i" JSON entry in either an Object or Array is the ID of the Ruby
274
+ Object being encoded. It is used when the :circular flag is set. It can appear
275
+ in either a JSON Object or in a JSON Array. If alone it represented a link to
276
+ the original Hash or JSON. If an added attribute it is the ID of the original
277
+ Object or Array. Examples are TBD.
@@ -83,7 +83,8 @@ static void dump_bignum(VALUE obj, Out out);
83
83
  static void dump_float(VALUE obj, Out out);
84
84
  static void dump_cstr(const char *str, size_t cnt, int is_sym, Out out);
85
85
  static void dump_hex(u_char c, Out out);
86
- static void dump_str(VALUE obj, Out out);
86
+ static void dump_str_comp(VALUE obj, Out out);
87
+ static void dump_str_obj(VALUE obj, Out out);
87
88
  static void dump_sym_comp(VALUE obj, Out out);
88
89
  static void dump_sym_obj(VALUE obj, Out out);
89
90
  static void dump_class_comp(VALUE obj, Out out);
@@ -350,10 +351,33 @@ dump_cstr(const char *str, size_t cnt, int is_sym, Out out) {
350
351
  }
351
352
 
352
353
  static void
353
- dump_str(VALUE obj, Out out) {
354
+ dump_str_comp(VALUE obj, Out out) {
354
355
  dump_cstr(StringValuePtr(obj), RSTRING_LEN(obj), 0, out);
355
356
  }
356
357
 
358
+ static void
359
+ dump_str_obj(VALUE obj, Out out) {
360
+ const char *s = StringValuePtr(obj);
361
+ size_t len = RSTRING_LEN(obj);
362
+
363
+ if (':' == *s) {
364
+ if (out->end - out->cur <= 6) {
365
+ grow(out, 6);
366
+ }
367
+ *out->cur++ = '{';
368
+ *out->cur++ = '"';
369
+ *out->cur++ = '^';
370
+ *out->cur++ = 's';
371
+ *out->cur++ = '"';
372
+ *out->cur++ = ':';
373
+ dump_cstr(s, len, 0, out);
374
+ *out->cur++ = '}';
375
+ *out->cur = '\0';
376
+ } else {
377
+ dump_cstr(s, len, 0, out);
378
+ }
379
+ }
380
+
357
381
  static void
358
382
  dump_sym_comp(VALUE obj, Out out) {
359
383
  const char *sym = rb_id2name(SYM2ID(obj));
@@ -373,7 +397,7 @@ dump_sym_obj(VALUE obj, Out out) {
373
397
  *out->cur++ = '{';
374
398
  *out->cur++ = '"';
375
399
  *out->cur++ = '^';
376
- *out->cur++ = 's';
400
+ *out->cur++ = 'm';
377
401
  *out->cur++ = '"';
378
402
  *out->cur++ = ':';
379
403
  dump_cstr(sym, len, 0, out);
@@ -456,7 +480,7 @@ hash_cb_strict(VALUE key, VALUE value, Out out) {
456
480
  }
457
481
  fill_indent(out, depth);
458
482
  if (rb_type(key) == T_STRING) {
459
- dump_str(key, out);
483
+ dump_str_comp(key, out);
460
484
  *out->cur++ = ':';
461
485
  dump_val(value, depth, out);
462
486
  } else {
@@ -479,7 +503,7 @@ hash_cb_object(VALUE key, VALUE value, Out out) {
479
503
  fill_indent(out, depth);
480
504
  // TBD if key is a string else dump with unique key for and entry array
481
505
  if (rb_type(key) == T_STRING) {
482
- dump_str(key, out);
506
+ dump_str_obj(key, out);
483
507
  *out->cur++ = ':';
484
508
  dump_val(value, depth, out);
485
509
  } else if (rb_type(key) == T_SYMBOL) {
@@ -778,7 +802,15 @@ dump_val(VALUE obj, int depth, Out out) {
778
802
  case T_FIXNUM: dump_fixnum(obj, out); break;
779
803
  case T_FLOAT: dump_float(obj, out); break;
780
804
  case T_BIGNUM: dump_bignum(obj, out); break;
781
- case T_STRING: dump_str(obj, out); break;
805
+ case T_STRING:
806
+ switch (out->opts->mode) {
807
+ case StrictMode:
808
+ case NullMode:
809
+ case CompatMode: dump_str_comp(obj, out); break;
810
+ case ObjectMode:
811
+ default: dump_str_obj(obj, out); break;
812
+ }
813
+ break;
782
814
  case T_SYMBOL:
783
815
  switch (out->opts->mode) {
784
816
  case StrictMode: raise_strict(obj); break;
@@ -112,15 +112,14 @@ next_white(ParseInfo pi) {
112
112
  }
113
113
 
114
114
  inline static VALUE
115
- resolve_classname(VALUE mod, const char *class_name, int create) {
115
+ resolve_classname(VALUE mod, const char *class_name, int auto_define) {
116
116
  VALUE clas;
117
117
  ID ci = rb_intern(class_name);
118
118
 
119
- if (rb_const_defined_at(mod, ci) || !create) {
119
+ if (rb_const_defined_at(mod, ci) || !auto_define) {
120
120
  clas = rb_const_get_at(mod, ci);
121
121
  } else {
122
- //clas = rb_define_class_under(mod, class_name, oj_bag_clas);
123
- clas = rb_const_get_at(mod, ci); // TBD temp
122
+ clas = rb_define_class_under(mod, class_name, oj_bag_class);
124
123
  }
125
124
  return clas;
126
125
  }
@@ -139,10 +138,8 @@ classname2obj(const char *name, ParseInfo pi) {
139
138
  static VALUE
140
139
  classname2class(const char *name, ParseInfo pi) {
141
140
  VALUE clas;
142
- int create = 0; // TBD from options
143
-
144
- #if 1
145
141
  VALUE *slot;
142
+ int auto_define = (Yes == pi->options->auto_define);
146
143
 
147
144
  if (Qundef == (clas = oj_cache_get(oj_class_cache, name, &slot))) {
148
145
  char class_name[1024];
@@ -154,7 +151,10 @@ classname2class(const char *name, ParseInfo pi) {
154
151
  if (':' == *n) {
155
152
  *s = '\0';
156
153
  n++;
157
- if (Qundef == (clas = resolve_classname(clas, class_name, create))) {
154
+ if (':' != *n) {
155
+ raise_error("Invalid classname, expected another ':'", pi->str, pi->s);
156
+ }
157
+ if (Qundef == (clas = resolve_classname(clas, class_name, auto_define))) {
158
158
  return Qundef;
159
159
  }
160
160
  s = class_name;
@@ -163,31 +163,10 @@ classname2class(const char *name, ParseInfo pi) {
163
163
  }
164
164
  }
165
165
  *s = '\0';
166
- if (Qundef != (clas = resolve_classname(clas, class_name, create))) {
166
+ if (Qundef != (clas = resolve_classname(clas, class_name, auto_define))) {
167
167
  *slot = clas;
168
168
  }
169
169
  }
170
- #else
171
- char class_name[1024];
172
- char *s;
173
- const char *n = name;
174
-
175
- clas = rb_cObject;
176
- for (s = class_name; '\0' != *n; n++) {
177
- if (':' == *n) {
178
- *s = '\0';
179
- n++;
180
- if (Qundef == (clas = resolve_classname(clas, class_name, create))) {
181
- return Qundef;
182
- }
183
- s = class_name;
184
- } else {
185
- *s++ = *n;
186
- }
187
- }
188
- *s = '\0';
189
- clas = resolve_classname(clas, class_name, create);
190
- #endif
191
170
  return clas;
192
171
  }
193
172
 
@@ -314,7 +293,11 @@ read_obj(ParseInfo pi) {
314
293
  obj = read_next(pi, T_CLASS);
315
294
  key = Qundef;
316
295
  break;
317
- case 's': // Symbol
296
+ case 's': // String
297
+ obj = read_next(pi, T_STRING);
298
+ key = Qundef;
299
+ break;
300
+ case 'm': // Symbol
318
301
  obj = read_next(pi, T_SYMBOL);
319
302
  key = Qundef;
320
303
  break;
@@ -54,8 +54,10 @@ ID oj_tv_nsec_id;
54
54
  ID oj_tv_sec_id;
55
55
  ID oj_tv_usec_id;
56
56
 
57
+ VALUE oj_bag_class;
57
58
  VALUE oj_time_class;
58
59
 
60
+ static VALUE auto_define_sym;
59
61
  static VALUE circular_sym;
60
62
  static VALUE compat_sym;
61
63
  static VALUE encoding_sym;
@@ -72,6 +74,7 @@ static struct _Options default_options = {
72
74
  { '\0' }, // encoding
73
75
  0, // indent
74
76
  No, // circular
77
+ Yes, // auto_define
75
78
  ObjectMode, // mode
76
79
  };
77
80
 
@@ -81,6 +84,7 @@ static struct _Options default_options = {
81
84
  * - indent: [Fixnum] number of spaces to indent each element in an XML document
82
85
  * - encoding: [String] character encoding for the JSON file
83
86
  * - circular: [true|false|nil] support circular references while dumping
87
+ * - auto_define: [true|false|nil] automatically define classes if they do not exist
84
88
  * - mode: [:object|:strict|:compat|:null] load and dump modes to use for JSON
85
89
  * @return [Hash] all current option settings.
86
90
  */
@@ -92,6 +96,7 @@ get_def_opts(VALUE self) {
92
96
  rb_hash_aset(opts, encoding_sym, (0 == elen) ? Qnil : rb_str_new(default_options.encoding, elen));
93
97
  rb_hash_aset(opts, indent_sym, INT2FIX(default_options.indent));
94
98
  rb_hash_aset(opts, circular_sym, (Yes == default_options.circular) ? Qtrue : ((No == default_options.circular) ? Qfalse : Qnil));
99
+ rb_hash_aset(opts, auto_define_sym, (Yes == default_options.auto_define) ? Qtrue : ((No == default_options.auto_define) ? Qfalse : Qnil));
95
100
  switch (default_options.mode) {
96
101
  case StrictMode: rb_hash_aset(opts, mode_sym, strict_sym); break;
97
102
  case CompatMode: rb_hash_aset(opts, mode_sym, compat_sym); break;
@@ -109,7 +114,8 @@ get_def_opts(VALUE self) {
109
114
  * @param [Fixnum] :indent number of spaces to indent each element in an XML document
110
115
  * @param [String] :encoding character encoding for the JSON file
111
116
  * @param [true|false|nil] :circular support circular references while dumping
112
- * @parsm [:object|:strict|:compat|:null] load and dump mode to use for JSON
117
+ * @param [true|false|nil] :auto_define automatically define classes if they do not exist
118
+ * @param [:object|:strict|:compat|:null] load and dump mode to use for JSON
113
119
  * :strict raises an exception when a non-supported Object is
114
120
  * encountered. :compat attempts to extract variable values from an
115
121
  * Object using to_json() or to_hash() then it walks the Object's
@@ -122,6 +128,7 @@ static VALUE
122
128
  set_def_opts(VALUE self, VALUE opts) {
123
129
  struct _YesNoOpt ynos[] = {
124
130
  { circular_sym, &default_options.circular },
131
+ { auto_define_sym, &default_options.auto_define },
125
132
  { Qnil, 0 }
126
133
  };
127
134
  YesNoOpt o;
@@ -366,8 +373,10 @@ void Init_oj() {
366
373
  oj_tv_sec_id = rb_intern("tv_sec");
367
374
  oj_tv_usec_id = rb_intern("tv_usec");
368
375
 
376
+ oj_bag_class = rb_const_get_at(Oj, rb_intern("Bag"));
369
377
  oj_time_class = rb_const_get(rb_cObject, rb_intern("Time"));
370
378
 
379
+ auto_define_sym = ID2SYM(rb_intern("auto_define")); rb_ary_push(keep, auto_define_sym);
371
380
  circular_sym = ID2SYM(rb_intern("circular")); rb_ary_push(keep, circular_sym);
372
381
  compat_sym = ID2SYM(rb_intern("compat")); rb_ary_push(keep, compat_sym);
373
382
  encoding_sym = ID2SYM(rb_intern("encoding")); rb_ary_push(keep, encoding_sym);
@@ -78,10 +78,11 @@ typedef enum {
78
78
  } Mode;
79
79
 
80
80
  typedef struct _Options {
81
- char encoding[64]; // encoding, stored in the option to avoid GC invalidation in default values
82
- int indent; // indention for dump, default 2
83
- char circular; // YesNo
84
- char mode; // Mode
81
+ char encoding[64]; // encoding, stored in the option to avoid GC invalidation in default values
82
+ int indent; // indention for dump, default 2
83
+ char circular; // YesNo
84
+ char auto_define; // YesNo
85
+ char mode; // Mode
85
86
  } *Options;
86
87
 
87
88
  extern VALUE oj_parse(char *json, Options options);
@@ -92,6 +93,7 @@ extern void _oj_raise_error(const char *msg, const char *xml, const char *curren
92
93
 
93
94
  extern VALUE Oj;
94
95
 
96
+ extern VALUE oj_bag_class;
95
97
  extern VALUE oj_time_class;
96
98
 
97
99
  extern ID oj_at_id;
data/lib/oj.rb CHANGED
@@ -31,6 +31,7 @@ module Oj
31
31
  @@keep = []
32
32
  end
33
33
 
34
- #require 'ox/version'
34
+ require 'oj/version'
35
+ require 'oj/bag'
35
36
 
36
37
  require 'oj/oj' # C extension
@@ -0,0 +1,92 @@
1
+
2
+ module Oj
3
+
4
+ # A generic class that is used only for storing attributes. It is the base
5
+ # Class for auto-generated classes in the storage system. Instance variables
6
+ # are added using the instance_variable_set() method. All instance variables
7
+ # can be accessed using the variable name (without the @ prefix). No setters
8
+ # are provided as the Class is intended for reading only.
9
+ class Bag
10
+
11
+ # The initializer can take multiple arguments in the form of key values
12
+ # where the key is the variable name and the value is the variable
13
+ # value. This is intended for testing purposes only.
14
+ # @example Oj::Bag.new(:@x => 42, :@y => 57)
15
+ # @param [Hash] args instance variable symbols and their values
16
+ def initialize(args={ })
17
+ args.each do |k,v|
18
+ self.instance_variable_set(k, v)
19
+ end
20
+ end
21
+
22
+ # Replaces the Object.respond_to?() method.
23
+ # @param [Symbol] m method symbol
24
+ # @return [Boolean] true for any method that matches an instance
25
+ # variable reader, otherwise false.
26
+ def respond_to?(m)
27
+ return true if super
28
+ at_m = ('@' + m.to_s).to_sym
29
+ instance_variables.include?(at_m)
30
+ end
31
+
32
+ # Handles requests for variable values. Others cause an Exception to be
33
+ # raised.
34
+ # @param [Symbol] m method symbol
35
+ # @return [Boolean] the value of the specified instance variable.
36
+ # @raise [ArgumentError] if an argument is given. Zero arguments expected.
37
+ # @raise [NoMethodError] if the instance variable is not defined.
38
+ def method_missing(m, *args, &block)
39
+ raise ArgumentError.new("wrong number of arguments (#{args.size} for 0) to method #{m}") unless args.nil? or args.empty?
40
+ at_m = ('@' + m.to_s).to_sym
41
+ raise NoMethodError.new("undefined method #{m}", m) unless instance_variable_defined?(at_m)
42
+ instance_variable_get(at_m)
43
+ end
44
+
45
+ # Replaces eql?() with something more reasonable for this Class.
46
+ # @param [Object] other Object to compare self to
47
+ # @return [Boolean] true if each variable and value are the same, otherwise false.
48
+ def eql?(other)
49
+ return false if (other.nil? or self.class != other.class)
50
+ ova = other.instance_variables
51
+ iv = instance_variables
52
+ return false if ova.size != iv.size
53
+ iv.each do |vid|
54
+ return false if instance_variable_get(vid) != other.instance_variable_get(vid)
55
+ end
56
+ true
57
+ end
58
+ alias == eql?
59
+
60
+ # Define a new class based on the Oj::Bag class. This is used internally in
61
+ # the Oj module and is available to service wrappers that receive XML
62
+ # requests that include Objects of Classes not defined in the storage
63
+ # process.
64
+ # @param [String] classname Class name or symbol that includes Module names.
65
+ # @return [Object] an instance of the specified Class.
66
+ # @raise [NameError] if the classname is invalid.
67
+ def self.define_class(classname)
68
+ classname = classname.to_s unless classname.is_a?(String)
69
+ tokens = classname.split('::').map { |n| n.to_sym }
70
+ raise NameError.new("Invalid classname '#{classname}") if tokens.empty?
71
+ m = Object
72
+ tokens[0..-2].each do |sym|
73
+ if m.const_defined?(sym)
74
+ m = m.const_get(sym)
75
+ else
76
+ c = Module.new
77
+ m.const_set(sym, c)
78
+ m = c
79
+ end
80
+ end
81
+ sym = tokens[-1]
82
+ if m.const_defined?(sym)
83
+ c = m.const_get(sym)
84
+ else
85
+ c = Class.new(Oj::Bag)
86
+ m.const_set(sym, c)
87
+ end
88
+ c
89
+ end
90
+
91
+ end # Bag
92
+ end # Oj
@@ -1,5 +1,5 @@
1
1
 
2
2
  module Oj
3
3
  # Current version of the module.
4
- VERSION = '0.7.0'
4
+ VERSION = '0.8.0'
5
5
  end
@@ -50,6 +50,7 @@ class Juice < ::Test::Unit::TestCase
50
50
  :encoding=>nil,
51
51
  :indent=>0,
52
52
  :circular=>false,
53
+ :auto_define=>true,
53
54
  :mode=>:object})
54
55
  end
55
56
 
@@ -58,11 +59,13 @@ class Juice < ::Test::Unit::TestCase
58
59
  :encoding=>nil,
59
60
  :indent=>0,
60
61
  :circular=>false,
62
+ :auto_define=>true,
61
63
  :mode=>:object}
62
64
  o2 = {
63
65
  :encoding=>"UTF-8",
64
66
  :indent=>4,
65
67
  :circular=>true,
68
+ :auto_define=>false,
66
69
  :mode=>:compat}
67
70
  o3 = { :indent => 4 }
68
71
  Oj.default_options = o2
@@ -109,6 +112,11 @@ class Juice < ::Test::Unit::TestCase
109
112
  dump_and_load("a\u0041", false)
110
113
  end
111
114
 
115
+ def test_string_object
116
+ dump_and_load('abc', false)
117
+ dump_and_load(':abc', false)
118
+ end
119
+
112
120
  def test_encode
113
121
  Oj.default_options = { :encoding => 'UTF-8' }
114
122
  dump_and_load("ぴーたー", false)
@@ -348,6 +356,17 @@ class Juice < ::Test::Unit::TestCase
348
356
  assert_equal(e, e2);
349
357
  end
350
358
 
359
+ def test_bag
360
+ json = %{{
361
+ "^o":"Jem",
362
+ "x":true,
363
+ "y":58 }}
364
+ obj = Oj.load(json, :mode => :object)
365
+ assert_equal('Jem', obj.class.name)
366
+ assert_equal(true, obj.x)
367
+ assert_equal(58, obj.y)
368
+ end
369
+
351
370
  def dump_and_load(obj, trace=false)
352
371
  json = Oj.dump(obj, :indent => 2)
353
372
  puts json if trace
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oj
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-26 00:00:00.000000000 Z
12
+ date: 2012-02-27 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: ! 'The fastest JSON parser and object serializer. '
15
15
  email: peter@ohler.com
@@ -19,6 +19,7 @@ extensions:
19
19
  extra_rdoc_files:
20
20
  - README.md
21
21
  files:
22
+ - lib/oj/bag.rb
22
23
  - lib/oj/version.rb
23
24
  - lib/oj.rb
24
25
  - ext/oj/extconf.rb