Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

changed json object format

  • Loading branch information...
commit afe4e9923dbdcd6f11ad2055a55a769379735033 1 parent 2fd7af9
Peter Ohler authored
View
14 README.md
@@ -16,19 +16,19 @@ A fast JSON parser and Object marshaller as a Ruby gem.
## <a name="links">Links of Interest</a>
-## <a name="release">Release Notes</a>
+*Fast XML parser and marshaller on RubyGems*: https://rubygems.org/gems/ox
-### Release 0.6.0
+*Fast XML parser and marshaller on GitHub*: https://rubygems.org/gems/ox
-- supports arbitrary Object dumping/serialization
+## <a name="release">Release Notes</a>
-- to_hash() method called if the Object responds to to_hash and the result is converted to JSON
+### Release 0.7.0
-- to_json() method called if the Object responds to to_json
+- changed the object JSON format
-- almost any Object can be dumped, including Exceptions (not including Thread, Mutex and Objects that only make sense within a process)
+- serialized Ruby Objects can now be deserialized
-- default options have been added
+- improved performance testing
## <a name="description">Description</a>
View
394 ext/oj/dump.c
@@ -69,11 +69,11 @@ typedef struct _Out {
int indent;
int depth; // used by dumpHash
Options opts;
- VALUE obj;
uint32_t hash_cnt;
} *Out;
static void dump_obj_to_json(VALUE obj, Options copts, Out out);
+static void raise_strict(VALUE obj);
static void dump_val(VALUE obj, int depth, Out out);
static void dump_nil(Out out);
static void dump_true(Out out);
@@ -81,21 +81,28 @@ static void dump_false(Out out);
static void dump_fixnum(VALUE obj, Out out);
static void dump_bignum(VALUE obj, Out out);
static void dump_float(VALUE obj, Out out);
-static void dump_cstr(const char *str, int cnt, Out out);
+static void dump_cstr(const char *str, size_t cnt, int is_sym, Out out);
static void dump_hex(u_char c, Out out);
static void dump_str(VALUE obj, Out out);
-static void dump_sym(VALUE obj, Out out);
-static void dump_class(VALUE obj, Out out);
+static void dump_sym_comp(VALUE obj, Out out);
+static void dump_sym_obj(VALUE obj, Out out);
+static void dump_class_comp(VALUE obj, Out out);
+static void dump_class_obj(VALUE obj, Out out);
static void dump_array(VALUE obj, int depth, Out out);
-static void dump_hash(VALUE obj, int depth, Out out);
-static void dump_data(VALUE obj, Out out);
-static void dump_object(VALUE obj, int depth, Out out);
+static int hash_cb_strict(VALUE key, VALUE value, Out out);
+static int hash_cb_object(VALUE key, VALUE value, Out out);
+static void dump_hash(VALUE obj, int depth, int mode, Out out);
+static void dump_time(VALUE obj, Out out);
+static void dump_data_comp(VALUE obj, Out out);
+static void dump_data_obj(VALUE obj, Out out);
+static void dump_obj_comp(VALUE obj, int depth, Out out);
+static void dump_obj_obj(VALUE obj, int depth, Out out);
static int dump_attr_cb(ID key, VALUE value, Out out);
static void dump_obj_attrs(VALUE obj, int with_class, int depth, Out out);
static void grow(Out out, size_t len);
-static int is_json_friendly(const u_char *str, int len);
-static int json_friendly_size(const u_char *str, int len);
+static int is_json_friendly(const u_char *str, size_t len);
+static size_t json_friendly_size(const u_char *str, size_t len);
static const char hex_chars[17] = "0123456789abcdef";
@@ -111,7 +118,7 @@ uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu\
uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu";
inline static int
-is_json_friendly(const u_char *str, int len) {
+is_json_friendly(const u_char *str, size_t len) {
for (; 0 < len; str++, len--) {
if ('o' != json_friendly_chars[*str]) {
return 0;
@@ -120,8 +127,8 @@ is_json_friendly(const u_char *str, int len) {
return 1;
}
-inline static int
-json_friendly_size(const u_char *str, int len) {
+inline static size_t
+json_friendly_size(const u_char *str, size_t len) {
int cnt = 0;
for (; 0 < len; str++, len--) {
@@ -278,15 +285,18 @@ dump_float(VALUE obj, Out out) {
}
static void
-dump_cstr(const char *str, int cnt, Out out) {
- int size = json_friendly_size((u_char*)str, cnt);
+dump_cstr(const char *str, size_t cnt, int is_sym, Out out) {
+ size_t size = json_friendly_size((u_char*)str, cnt);
if (cnt == size) {
- cnt += 2;
+ cnt += 2 + is_sym;
if (out->end - out->cur <= (long)cnt) {
grow(out, cnt);
}
*out->cur++ = '"';
+ if (is_sym) {
+ *out->cur++ = ':';
+ }
for (; '\0' != *str; str++) {
*out->cur++ = *str;
}
@@ -297,6 +307,9 @@ dump_cstr(const char *str, int cnt, Out out) {
grow(out, size);
}
*out->cur++ = '"';
+ if (is_sym) {
+ *out->cur++ = ':';
+ }
for (; 0 < cnt; cnt--, str++) {
switch (json_friendly_chars[(u_char)*str]) {
case 'o':
@@ -338,46 +351,63 @@ dump_cstr(const char *str, int cnt, Out out) {
static void
dump_str(VALUE obj, Out out) {
- dump_cstr(StringValuePtr(obj), (int)RSTRING_LEN(obj), out);
+ dump_cstr(StringValuePtr(obj), RSTRING_LEN(obj), 0, out);
}
static void
-dump_sym(VALUE obj, Out out) {
+dump_sym_comp(VALUE obj, Out out) {
const char *sym = rb_id2name(SYM2ID(obj));
- dump_cstr(sym, (int)strlen(sym), out);
+ dump_cstr(sym, strlen(sym), 0, out);
}
static void
-dump_class(VALUE obj, Out out) {
- switch (out->opts->mode) {
- case StrictMode:
- rb_raise(rb_eTypeError, "Failed to dump class %s to JSON.\n", rb_class2name(obj));
- break;
- case NullMode:
- dump_nil(out);
- break;
- case ObjectMode:
- case CompatMode:
- default:
- {
- const char *s = rb_class2name(obj);
- size_t len = strlen(s);
- size_t size = len + 20;
-
- if (out->end - out->cur <= (long)size) {
- grow(out, size);
- }
- memcpy(out->cur, "{\"*\":\"Class\",\"-\":\"", 18);
- out->cur += 18;
- memcpy(out->cur, s, len);
- out->cur += len;
- *out->cur++ = '"';
- *out->cur++ = '}';
- *out->cur = '\0';
- break;
+dump_sym_obj(VALUE obj, Out out) {
+ const char *sym = rb_id2name(SYM2ID(obj));
+ size_t len = strlen(sym);
+
+ if (':' == *sym) {
+ if (out->end - out->cur <= 6) {
+ grow(out, 6);
}
+ *out->cur++ = '{';
+ *out->cur++ = '"';
+ *out->cur++ = '^';
+ *out->cur++ = 's';
+ *out->cur++ = '"';
+ *out->cur++ = ':';
+ dump_cstr(sym, len, 0, out);
+ *out->cur++ = '}';
+ *out->cur = '\0';
+ } else {
+ dump_cstr(sym, len, 1, out);
+ }
+}
+
+static void
+dump_class_comp(VALUE obj, Out out) {
+ const char *s = rb_class2name(obj);
+
+ dump_cstr(s, strlen(s), 0, out);
+}
+
+static void
+dump_class_obj(VALUE obj, Out out) {
+ const char *s = rb_class2name(obj);
+ size_t len = strlen(s);
+
+ if (out->end - out->cur <= 6) {
+ grow(out, 6);
}
+ *out->cur++ = '{';
+ *out->cur++ = '"';
+ *out->cur++ = '^';
+ *out->cur++ = 'c';
+ *out->cur++ = '"';
+ *out->cur++ = ':';
+ dump_cstr(s, len, 0, out);
+ *out->cur++ = '}';
+ *out->cur = '\0';
}
static void
@@ -417,7 +447,29 @@ dump_array(VALUE a, int depth, Out out) {
}
static int
-hash_cb(VALUE key, VALUE value, Out out) {
+hash_cb_strict(VALUE key, VALUE value, Out out) {
+ int depth = out->depth;
+ long size = depth * out->indent + 1;
+
+ if (out->end - out->cur <= size) {
+ grow(out, size);
+ }
+ fill_indent(out, depth);
+ if (rb_type(key) == T_STRING) {
+ dump_str(key, out);
+ *out->cur++ = ':';
+ dump_val(value, depth, out);
+ } else {
+ rb_raise(rb_eTypeError, "In :strict mode all Hash keys must be Strings.");
+ }
+ out->depth = depth;
+ *out->cur++ = ',';
+
+ return ST_CONTINUE;
+}
+
+static int
+hash_cb_object(VALUE key, VALUE value, Out out) {
int depth = out->depth;
long size = depth * out->indent + 1;
@@ -430,8 +482,10 @@ hash_cb(VALUE key, VALUE value, Out out) {
dump_str(key, out);
*out->cur++ = ':';
dump_val(value, depth, out);
- } else if (CompatMode != out->opts->mode && ObjectMode != out->opts->mode) {
- rb_raise(rb_eTypeError, "Failed to dump %s Object, a Hash key, to JSON in strict mode.\n", rb_class2name(key));
+ } else if (rb_type(key) == T_SYMBOL) {
+ dump_sym_obj(key, out);
+ *out->cur++ = ':';
+ dump_val(value, depth, out);
} else {
int d2 = depth + 1;
long s2 = size + out->indent + 1;
@@ -439,10 +493,11 @@ hash_cb(VALUE key, VALUE value, Out out) {
int started = 0;
u_char b;
- if (out->end - out->cur <= s2 + 14) {
- grow(out, s2 + 14);
+ if (out->end - out->cur <= s2 + 15) {
+ grow(out, s2 + 15);
}
*out->cur++ = '"';
+ *out->cur++ = '^';
*out->cur++ = '#';
out->hash_cnt++;
for (i = 28; 0 <= i; i -= 4) {
@@ -478,7 +533,7 @@ hash_cb(VALUE key, VALUE value, Out out) {
}
static void
-dump_hash(VALUE obj, int depth, Out out) {
+dump_hash(VALUE obj, int depth, int mode, Out out) {
int cnt = (int)RHASH_SIZE(obj);
if (out->end - out->cur <= 2) {
@@ -491,8 +546,14 @@ dump_hash(VALUE obj, int depth, Out out) {
size_t size = depth * out->indent + 2;
out->depth = depth + 1;
- rb_hash_foreach(obj, hash_cb, (VALUE)out);
- out->cur--; // backup to overwrite last comma
+ if (ObjectMode == mode) {
+ rb_hash_foreach(obj, hash_cb_object, (VALUE)out);
+ } else {
+ rb_hash_foreach(obj, hash_cb_strict, (VALUE)out);
+ }
+ if (',' == *(out->cur - 1)) {
+ out->cur--; // backup to overwrite last comma
+ }
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
@@ -503,95 +564,95 @@ dump_hash(VALUE obj, int depth, Out out) {
}
static void
-dump_data(VALUE obj, Out out) {
+dump_time(VALUE obj, Out out) {
+ char buf[64];
+ char *b = buf + sizeof(buf) - 1;
+ time_t sec = NUM2LONG(rb_funcall2(obj, oj_tv_sec_id, 0, 0));
+ long usec = NUM2LONG(rb_funcall2(obj, oj_tv_usec_id, 0, 0));
+ char *dot = b - 7;
+ long size;
+
+ *b-- = '\0';
+ for (; dot < b; b--, usec /= 10) {
+ *b = '0' + (usec % 10);
+ }
+ *b-- = '.';
+ for (; 0 < sec; b--, sec /= 10) {
+ *b = '0' + (sec % 10);
+ }
+ b++;
+ size = sizeof(buf) - (b - buf) - 1;
+ if (out->end - out->cur <= size) {
+ grow(out, size);
+ }
+ memcpy(out->cur, b, size);
+ out->cur += size;
+ *out->cur = '\0';
+}
+
+static void
+dump_data_comp(VALUE obj, Out out) {
VALUE clas = rb_obj_class(obj);
- switch (out->opts->mode) {
- case StrictMode:
- rb_raise(rb_eTypeError, "Failed to dump %s Object to JSON in strict mode.\n", rb_class2name(clas));
- break;
- case NullMode:
+ if (rb_cTime == clas) {
+ dump_time(obj, out);
+ } else {
dump_nil(out);
- break;
- case ObjectMode:
- case CompatMode:
- default:
- if (rb_cTime == clas) {
- char buf[64];
- char *b = buf + sizeof(buf) - 1;
- time_t sec = NUM2LONG(rb_funcall2(obj, oj_tv_sec_id, 0, 0));
- long usec = NUM2LONG(rb_funcall2(obj, oj_tv_usec_id, 0, 0));
- char *dot = b - 7;
- long size;
-
- *b-- = '\0';
- for (; dot < b; b--, usec /= 10) {
- *b = '0' + (usec % 10);
- }
- *b-- = '.';
- for (; 0 < sec; b--, sec /= 10) {
- *b = '0' + (sec % 10);
- }
- b++;
- size = sizeof(buf) - (b - buf) - 1;
- if (out->end - out->cur <= size + 20) {
- grow(out, size + 20);
- }
- memcpy(out->cur, "{\"*\":\"Time\",\"-\":", 16);
- out->cur += 16;
- memcpy(out->cur, b, size);
- out->cur += size;
- *out->cur++ = '}';
- *out->cur = '\0';
- } else {
- dump_nil(out);
- }
}
}
static void
-dump_object(VALUE obj, int depth, Out out) {
- if (ObjectMode == out->opts->mode) {
- dump_obj_attrs(obj, 1, depth, out);
+dump_data_obj(VALUE obj, Out out) {
+ VALUE clas = rb_obj_class(obj);
+
+ if (rb_cTime == clas) {
+ if (out->end - out->cur <= 6) {
+ grow(out, 6);
+ }
+ *out->cur++ = '{';
+ *out->cur++ = '"';
+ *out->cur++ = '^';
+ *out->cur++ = 't';
+ *out->cur++ = '"';
+ *out->cur++ = ':';
+ dump_time(obj, out);
+ *out->cur++ = '}';
+ *out->cur = '\0';
} else {
- switch (out->opts->mode) {
- case StrictMode:
- rb_raise(rb_eTypeError, "Failed to dump %s Object to JSON in strict mode.\n", rb_class2name(rb_obj_class(obj)));
- break;
- case NullMode:
- dump_nil(out);
- break;
- case ObjectMode:
- dump_obj_attrs(obj, 0, depth, out);
- break;
- case CompatMode:
- default:
- if (rb_respond_to(obj, oj_to_hash_id)) {
- VALUE h = rb_funcall(obj, oj_to_hash_id, 0);
+ dump_nil(out);
+ }
+}
+
+static void
+dump_obj_comp(VALUE obj, int depth, Out out) {
+ if (rb_respond_to(obj, oj_to_hash_id)) {
+ VALUE h = rb_funcall(obj, oj_to_hash_id, 0);
- if (T_HASH != rb_type(h)) {
- rb_raise(rb_eTypeError, "%s.to_hash() did not return a Hash.\n", rb_class2name(rb_obj_class(obj)));
- }
- dump_hash(h, depth, out);
- } else if (rb_respond_to(obj, oj_to_json_id)) {
- VALUE rs = rb_funcall(obj, oj_to_json_id, 0);
- const char *s = StringValuePtr(rs);
- int len = (int)RSTRING_LEN(rs);
-
- if (out->end - out->cur <= len) {
- grow(out, len);
- }
- memcpy(out->cur, s, len);
- out->cur += len;
- } else {
- dump_obj_attrs(obj, 0, depth, out);
- }
- break;
+ if (T_HASH != rb_type(h)) {
+ rb_raise(rb_eTypeError, "%s.to_hash() did not return a Hash.\n", rb_class2name(rb_obj_class(obj)));
}
+ dump_hash(h, depth, out->opts->mode, out);
+ } else if (rb_respond_to(obj, oj_to_json_id)) {
+ VALUE rs = rb_funcall(obj, oj_to_json_id, 0);
+ const char *s = StringValuePtr(rs);
+ int len = (int)RSTRING_LEN(rs);
+
+ if (out->end - out->cur <= len) {
+ grow(out, len);
+ }
+ memcpy(out->cur, s, len);
+ out->cur += len;
+ } else {
+ dump_obj_attrs(obj, 0, depth, out);
}
*out->cur = '\0';
}
+inline static void
+dump_obj_obj(VALUE obj, int depth, Out out) {
+ dump_obj_attrs(obj, 1, depth, out);
+}
+
static int
dump_attr_cb(ID key, VALUE value, Out out) {
int depth = out->depth;
@@ -601,13 +662,18 @@ dump_attr_cb(ID key, VALUE value, Out out) {
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
+ fill_indent(out, depth);
if ('@' == *attr) {
attr++;
+ dump_cstr(attr, strlen(attr), 0, out);
} else {
- // TBD handle unusual exception mesg data
+ char buf[32];
+
+ *buf = '~';
+ strncpy(buf + 1, attr, sizeof(buf) - 2);
+ buf[sizeof(buf) - 1] = '\0';
+ dump_cstr(buf, strlen(buf), 0, out);
}
- fill_indent(out, depth);
- dump_cstr(attr, (int)strlen(attr) - 1, out);
*out->cur++ = ':';
dump_val(value, depth, out);
out->depth = depth;
@@ -628,17 +694,18 @@ dump_obj_attrs(VALUE obj, int with_class, int depth, Out out) {
if (with_class) {
const char *class_name = rb_class2name(rb_obj_class(obj));
int clen = (int)strlen(class_name);
-
- size = d2 * out->indent + clen + 9;
+
+ size = d2 * out->indent + clen + 10;
if (out->end - out->cur <= (long)size) {
grow(out, size);
}
fill_indent(out, d2);
*out->cur++ = '"';
- *out->cur++ = '*';
+ *out->cur++ = '^';
+ *out->cur++ = 'o';
*out->cur++ = '"';
*out->cur++ = ':';
- dump_cstr(class_name, clen, out);
+ dump_cstr(class_name, clen, 0, out);
}
{
int cnt;
@@ -672,10 +739,15 @@ dump_obj_attrs(VALUE obj, int with_class, int depth, Out out) {
attr = rb_id2name(vid);
if ('@' == *attr) {
attr++;
+ dump_cstr(attr, strlen(attr), out);
} else {
- // TBD handle unusual exception mesg data
+ char buf[32];
+
+ *buf = '~';
+ strncpy(buf + 1, attr, sizeof(buf) - 2);
+ buf[sizeof(buf) - 1] = '\0';
+ dump_cstr(buf, strlen(attr) + 1, out);
}
- dump_cstr(attr, (int)strlen(attr) - 1, out);
*out->cur++ = ':';
dump_val(rb_ivar_get(obj, vid), d2, out);
if (out->end - out->cur <= 2) {
@@ -693,6 +765,11 @@ dump_obj_attrs(VALUE obj, int with_class, int depth, Out out) {
}
static void
+raise_strict(VALUE obj) {
+ rb_raise(rb_eTypeError, "Failed to dump %s Object to JSON in strict mode.\n", rb_class2name(rb_obj_class(obj)));
+}
+
+static void
dump_val(VALUE obj, int depth, Out out) {
switch (rb_type(obj)) {
case T_NIL: dump_nil(out); break;
@@ -702,12 +779,44 @@ dump_val(VALUE obj, int depth, Out out) {
case T_FLOAT: dump_float(obj, out); break;
case T_BIGNUM: dump_bignum(obj, out); break;
case T_STRING: dump_str(obj, out); break;
- case T_SYMBOL: dump_sym(obj, out); break;
+ case T_SYMBOL:
+ switch (out->opts->mode) {
+ case StrictMode: raise_strict(obj); break;
+ case NullMode: dump_nil(out); break;
+ case CompatMode: dump_sym_comp(obj, out); break;
+ case ObjectMode:
+ default: dump_sym_obj(obj, out); break;
+ }
+ break;
case T_ARRAY: dump_array(obj, depth, out); break;
- case T_HASH: dump_hash(obj, depth, out); break;
- case T_CLASS: dump_class(obj, out); break;
- case T_OBJECT: dump_object(obj, depth, out); break;
- case T_DATA: dump_data(obj, out); break;
+ case T_HASH: dump_hash(obj, depth, out->opts->mode, out); break;
+ case T_CLASS:
+ switch (out->opts->mode) {
+ case StrictMode: raise_strict(obj); break;
+ case NullMode: dump_nil(out); break;
+ case CompatMode: dump_class_comp(obj, out); break;
+ case ObjectMode:
+ default: dump_class_obj(obj, out); break;
+ }
+ break;
+ case T_OBJECT:
+ switch (out->opts->mode) {
+ case StrictMode: raise_strict(obj); break;
+ case NullMode: dump_nil(out); break;
+ case CompatMode: dump_obj_comp(obj, depth, out); break;
+ case ObjectMode:
+ default: dump_obj_obj(obj, depth, out); break;
+ }
+ break;
+ case T_DATA:
+ switch (out->opts->mode) {
+ case StrictMode: raise_strict(obj); break;
+ case NullMode: dump_nil(out); break;
+ case CompatMode: dump_data_comp(obj, out); break;
+ case ObjectMode:
+ default: dump_data_obj(obj, out); break;
+ }
+ break;
case T_REGEXP:
// TBD
rb_raise(rb_eNotImpError, "Failed to dump '%s' Object (%02x)\n",
@@ -728,7 +837,6 @@ dump_obj_to_json(VALUE obj, Options copts, Out out) {
// out->circ_cache = 0;
// out->circ_cnt = 0;
out->opts = copts;
- out->obj = obj;
out->hash_cnt = 0;
/* if (Yes == copts->circular) {
ox_cache8_new(&out->circ_cache);
View
221 ext/oj/load.c
@@ -36,6 +36,10 @@
#include "ruby.h"
#include "oj.h"
+enum {
+ TIME_HINT = 0x0100,
+};
+
typedef struct _ParseInfo {
char *str; /* buffer being read from */
char *s; /* current position in buffer */
@@ -48,11 +52,12 @@ typedef struct _ParseInfo {
} *ParseInfo;
static VALUE classname2class(const char *name, ParseInfo pi);
-static VALUE read_next(ParseInfo pi);
+static VALUE read_next(ParseInfo pi, int hint);
static VALUE read_obj(ParseInfo pi);
static VALUE read_array(ParseInfo pi);
-static VALUE read_str(ParseInfo pi);
+static VALUE read_str(ParseInfo pi, int hint);
static VALUE read_num(ParseInfo pi);
+static VALUE read_time(ParseInfo pi);
static VALUE read_true(ParseInfo pi);
static VALUE read_false(ParseInfo pi);
static VALUE read_nil(ParseInfo pi);
@@ -136,8 +141,9 @@ classname2class(const char *name, ParseInfo pi) {
VALUE clas;
int create = 0; // TBD from options
-#if 0
+#if 1
VALUE *slot;
+
if (Qundef == (clas = oj_cache_get(oj_class_cache, name, &slot))) {
char class_name[1024];
char *s;
@@ -148,7 +154,7 @@ classname2class(const char *name, ParseInfo pi) {
if (':' == *n) {
*s = '\0';
n++;
- if (Qundef == (clas = resolve_classname(clas, class_name, pi->effort))) {
+ if (Qundef == (clas = resolve_classname(clas, class_name, create))) {
return Qundef;
}
s = class_name;
@@ -157,7 +163,7 @@ classname2class(const char *name, ParseInfo pi) {
}
}
*s = '\0';
- if (Qundef != (clas = resolve_classname(clas, class_name, pi->effort))) {
+ if (Qundef != (clas = resolve_classname(clas, class_name, create))) {
*slot = clas;
}
}
@@ -202,7 +208,7 @@ oj_parse(char *json, Options options) {
pi.encoding = 0;
#endif
pi.options = options;
- if (Qundef == (obj = read_next(&pi))) {
+ if (Qundef == (obj = read_next(&pi, 0))) {
raise_error("no object read", pi.str, pi.s);
}
next_non_white(&pi); // skip white space
@@ -213,7 +219,7 @@ oj_parse(char *json, Options options) {
}
static VALUE
-read_next(ParseInfo pi) {
+read_next(ParseInfo pi, int hint) {
VALUE obj;
next_non_white(pi); // skip white space
@@ -225,7 +231,7 @@ read_next(ParseInfo pi) {
obj = read_array(pi);
break;
case '"':
- obj = read_str(pi);
+ obj = read_str(pi, hint);
break;
case '+':
case '-':
@@ -239,7 +245,11 @@ read_next(ParseInfo pi) {
case '7':
case '8':
case '9':
- obj = read_num(pi);
+ if (TIME_HINT == hint) {
+ obj = read_time(pi);
+ } else {
+ obj = read_num(pi);
+ }
break;
case 't':
obj = read_true(pi);
@@ -277,7 +287,9 @@ read_obj(ParseInfo pi) {
while (1) {
next_non_white(pi);
ks = 0;
- if ('"' != *pi->s || Qundef == (key = read_str(pi))) {
+ key = Qundef;
+ val = Qundef;
+ if ('"' != *pi->s || Qundef == (key = read_str(pi, 0))) {
raise_error("unexpected character", pi->str, pi->s);
}
next_non_white(pi);
@@ -286,43 +298,79 @@ read_obj(ParseInfo pi) {
} else {
raise_error("invalid format, expected :", pi->str, pi->s);
}
- if (Qundef == (val = read_next(pi))) {
- raise_error("unexpected character", pi->str, pi->s);
- }
- if (ObjectMode == pi->options->mode || CompatMode == pi->options->mode) {
+ if (T_STRING == rb_type(key)) {
ks = StringValuePtr(key);
- if ('*' == *ks && T_STRING == rb_type(val)) {
- if (Qundef == obj) {
- const char *classname = StringValuePtr(val);
-
- obj = classname2obj(classname, pi);
+ } else {
+ ks = 0;
+ }
+ if (0 != ks && Qundef == obj && ObjectMode == pi->options->mode) {
+ if ('^' == *ks && '\0' == ks[2]) { // special directions
+ switch (ks[1]) {
+ case 't': // Time
+ obj = read_next(pi, TIME_HINT); // raises if can not convert to Time
+ key = Qundef;
+ break;
+ case 'c': // Class
+ obj = read_next(pi, T_CLASS);
+ key = Qundef;
+ break;
+ case 's': // Symbol
+ obj = read_next(pi, T_SYMBOL);
+ key = Qundef;
+ break;
+ case 'o': // Object
+ obj = read_next(pi, T_OBJECT);
obj_type = T_OBJECT;
+ key = Qundef;
+ break;
+ case 'i': // Id for circular reference
+ // TBD
+ default:
+ // handle later
+ break;
}
- } else if ('#' == *ks &&
- (T_NONE == obj_type || T_HASH == obj_type) &&
- rb_type(val) == T_ARRAY && 2 == RARRAY_LEN(val)) {
+ }
+ }
+ if (Qundef != key) {
+ if (Qundef == val && Qundef == (val = read_next(pi, 0))) {
+ raise_error("unexpected character", pi->str, pi->s);
+ }
+ if (ObjectMode == pi->options->mode &&
+ 0 != ks && '^' == *ks && '#' == ks[1] &&
+ (T_NONE == obj_type || T_HASH == obj_type) &&
+ rb_type(val) == T_ARRAY && 2 == RARRAY_LEN(val)) { // Hash entry
VALUE *np = RARRAY_PTR(val);
key = *np;
val = *(np + 1);
- } else if ('~' == *ks) {
- // TBD id for circular references
}
- }
- if (Qundef == obj) {
- obj = rb_hash_new();
- obj_type = T_HASH;
- }
- if (T_OBJECT == obj_type) {
- char attr[256];
-
- // TBD use cache for this
- *attr = '@';
- strncpy(attr + 1, ks, sizeof(attr) - 2);
- attr[sizeof(attr) - 1] = '\0';
- rb_ivar_set(obj, rb_intern(attr), val);
- } else {
- rb_hash_aset(obj, key, val);
+ if (Qundef == obj) {
+ obj = rb_hash_new();
+ obj_type = T_HASH;
+ }
+ if (T_OBJECT == obj_type) {
+ VALUE *slot;
+ ID var_id;
+
+ if (Qundef == (var_id = oj_cache_get(oj_attr_cache, ks, &slot))) {
+ char attr[1024];
+
+ if ('~' == *ks) {
+ strncpy(attr, ks + 1, sizeof(attr) - 1);
+ } else {
+ *attr = '@';
+ strncpy(attr + 1, ks, sizeof(attr) - 2);
+ }
+ attr[sizeof(attr) - 1] = '\0';
+ var_id = rb_intern(attr);
+ *slot = var_id;
+ }
+ rb_ivar_set(obj, var_id, val);
+ } else if (T_HASH == obj_type) {
+ rb_hash_aset(obj, key, val);
+ } else {
+ raise_error("invalid Object format, too many Hash entries.", pi->str, pi->s);
+ }
}
next_non_white(pi);
if ('}' == *pi->s) {
@@ -331,6 +379,7 @@ read_obj(ParseInfo pi) {
} else if (',' == *pi->s) {
pi->s++;
} else {
+ //printf("*** '%s'\n", pi->s);
raise_error("invalid format, expected , or } while in an object", pi->str, pi->s);
}
}
@@ -349,7 +398,7 @@ read_array(ParseInfo pi) {
return a;
}
while (1) {
- if (Qundef == (e = read_next(pi))) {
+ if (Qundef == (e = read_next(pi, 0))) {
raise_error("unexpected character", pi->str, pi->s);
}
rb_ary_push(a, e);
@@ -367,16 +416,75 @@ read_array(ParseInfo pi) {
}
static VALUE
-read_str(ParseInfo pi) {
+read_str(ParseInfo pi, int hint) {
char *text = read_quoted_value(pi);
- VALUE s = rb_str_new2(text);
+ VALUE obj;
-#ifdef HAVE_RUBY_ENCODING_H
- if (0 != pi->encoding) {
- rb_enc_associate(s, pi->encoding);
+ if (ObjectMode != pi->options->mode) {
+ hint = T_STRING;
}
+ switch (hint) {
+ case T_CLASS:
+ obj = classname2class(text, pi);
+ break;
+ case T_OBJECT:
+ obj = classname2obj(text, pi);
+ break;
+ case T_STRING:
+ obj = rb_str_new2(text);
+#ifdef HAVE_RUBY_ENCODING_H
+ if (0 != pi->encoding) {
+ rb_enc_associate(obj, pi->encoding);
+ }
+#endif
+ break;
+ case T_SYMBOL:
+#ifdef HAVE_RUBY_ENCODING_H
+ if (0 != pi->encoding) {
+ obj = rb_str_new2(text);
+ rb_enc_associate(obj, pi->encoding);
+ obj = rb_funcall(obj, oj_to_sym_id, 0);
+ } else {
+ obj = ID2SYM(rb_intern(text));
+ }
+#else
+ obj = ID2SYM(rb_intern(text));
#endif
- return s;
+ break;
+ case 0:
+ default:
+ if (':' == *text) {
+ if (':' == text[1]) { // escaped :, it s string
+ obj = rb_str_new2(text + 1);
+#ifdef HAVE_RUBY_ENCODING_H
+ if (0 != pi->encoding) {
+ rb_enc_associate(obj, pi->encoding);
+ }
+#endif
+ } else { // Symbol
+#ifdef HAVE_RUBY_ENCODING_H
+ if (0 != pi->encoding) {
+ obj = rb_str_new2(text + 1);
+ rb_enc_associate(obj, pi->encoding);
+ obj = rb_funcall(obj, oj_to_sym_id, 0);
+ } else {
+ obj = ID2SYM(rb_intern(text + 1));
+ }
+#else
+ obj = ID2SYM(rb_intern(text + 1));
+#endif
+ }
+ } else {
+ obj = rb_str_new2(text);
+#ifdef HAVE_RUBY_ENCODING_H
+ if (0 != pi->encoding) {
+ rb_enc_associate(obj, pi->encoding);
+ }
+#endif
+ }
+ break;
+ }
+ return obj;
}
#ifdef RUBINIUS
@@ -459,6 +567,27 @@ read_num(ParseInfo pi) {
}
static VALUE
+read_time(ParseInfo pi) {
+ VALUE args[2];
+ long v = 0;
+ long v2 = 0;
+
+ for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) {
+ v = v * 10 + (*pi->s - '0');
+ }
+ if ('.' == *pi->s) {
+ pi->s++;
+ for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) {
+ v2 = v2 * 10 + (*pi->s - '0');
+ }
+ }
+ args[0] = LONG2NUM(v);
+ args[1] = LONG2NUM(v2);
+
+ return rb_funcall2(oj_time_class, oj_at_id, 2, args);
+}
+
+static VALUE
read_true(ParseInfo pi) {
pi->s++;
if ('r' != *pi->s || 'u' != *(pi->s + 1) || 'e' != *(pi->s + 2)) {
View
10 ext/oj/oj.c
@@ -45,12 +45,17 @@ void Init_oj();
VALUE Oj = Qnil;
+ID oj_at_id;
ID oj_instance_variables_id;
ID oj_to_hash_id;
ID oj_to_json_id;
+ID oj_to_sym_id;
+ID oj_tv_nsec_id;
ID oj_tv_sec_id;
ID oj_tv_usec_id;
+VALUE oj_time_class;
+
static VALUE circular_sym;
static VALUE compat_sym;
static VALUE encoding_sym;
@@ -352,12 +357,17 @@ void Init_oj() {
rb_define_module_function(Oj, "dump", dump, -1);
rb_define_module_function(Oj, "to_file", to_file, -1);
+ oj_at_id = rb_intern("at");
oj_instance_variables_id = rb_intern("instance_variables");
oj_to_hash_id = rb_intern("to_hash");
oj_to_json_id = rb_intern("to_json");
+ oj_to_sym_id = rb_intern("to_sym");
+ oj_tv_nsec_id = rb_intern("tv_nsec");
oj_tv_sec_id = rb_intern("tv_sec");
oj_tv_usec_id = rb_intern("tv_usec");
+ oj_time_class = rb_const_get(rb_cObject, rb_intern("Time"));
+
circular_sym = ID2SYM(rb_intern("circular")); rb_ary_push(keep, circular_sym);
compat_sym = ID2SYM(rb_intern("compat")); rb_ary_push(keep, compat_sym);
encoding_sym = ID2SYM(rb_intern("encoding")); rb_ary_push(keep, encoding_sym);
View
5 ext/oj/oj.h
@@ -92,9 +92,14 @@ extern void _oj_raise_error(const char *msg, const char *xml, const char *curren
extern VALUE Oj;
+extern VALUE oj_time_class;
+
+extern ID oj_at_id;
extern ID oj_instance_variables_id;
extern ID oj_to_hash_id;
extern ID oj_to_json_id;
+extern ID oj_to_sym_id;
+extern ID oj_tv_nsec_id;
extern ID oj_tv_sec_id;
extern ID oj_tv_usec_id;
View
2  lib/oj/version.rb
@@ -1,5 +1,5 @@
module Oj
# Current version of the module.
- VERSION = '0.6.0'
+ VERSION = '0.7.0'
end
View
24 notes
@@ -5,25 +5,11 @@
- next
- - implement load mode
- - add class and attr cache
- - test
- - rename simple.rb to tests.rb
- - perf_obj
- - add msgpack
- - compare all gems, not just one
- - maybe a matrix - nah
- - ox oj marshal
- *ox - 1.1 3.0
- oj 0.9 - 2.5
- marshal 0.3 0.3 -
-
-
- - Perf
- - for each
- - title
- - Proc
- -
+ - complete all types for dump and load in :object mode
+
+ - document format
+
+
- stream
- load
View
15 test/perf.rb
@@ -9,6 +9,15 @@ def add(title, op, &blk)
@items << Item.new(title, op, &blk)
end
+ def before(title, &blk)
+ @items.each do |i|
+ if title == i.title
+ i.set_before(&blk)
+ break
+ end
+ end
+ end
+
def run(iter)
base = Item.new(nil, nil) { }
base.run(iter, 0.0)
@@ -73,10 +82,16 @@ def initialize(title, op, &blk)
@duration = nil
@rate = nil
@error = nil
+ @before = nil
+ end
+
+ def set_before(&blk)
+ @before = blk
end
def run(iter, base)
begin
+ @before.call unless @before.nil?
start = Time.now
iter.times { @blk.call }
@duration = Time.now - start - base
View
131 test/perf_strict.rb
@@ -0,0 +1,131 @@
+#!/usr/bin/env ruby -wW1
+# encoding: UTF-8
+
+$: << '.'
+$: << File.join(File.dirname(__FILE__), "../lib")
+$: << File.join(File.dirname(__FILE__), "../ext")
+
+require 'optparse'
+require 'yajl'
+require 'perf'
+require 'json'
+require 'json/pure'
+require 'json/ext'
+require 'msgpack'
+require 'oj'
+require 'ox'
+
+class Jazz
+ def initialize()
+ @boolean = true
+ @number = 58
+ @string = "A string"
+ @array = [true, false, nil]
+ @hash = { 'one' => 1, 'two' => 2 }
+ end
+ def to_json(*) # Yajl and JSON have different signatures
+ %{
+ { "boolean":#{@boolean},
+ "number":#{@number},
+ "string":#{@string},
+ "array":#{@array},
+ "hash":#{@hash},
+ }
+}
+ end
+ def to_hash()
+ { 'boolean' => @boolean,
+ 'number' => @number,
+ 'string' => @string,
+ 'array' => @array,
+ 'hash' => @hash,
+ }
+ end
+ def to_msgpack(out)
+ out << MessagePack.pack(to_hash())
+ end
+end
+
+$indent = 0
+$iter = 100000
+$with_object = true
+$with_bignum = true
+$with_nums = true
+
+opts = OptionParser.new
+opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i }
+opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i }
+opts.on("-o", "without objects") { $with_object = false }
+opts.on("-b", "without bignum") { $with_bignum = false }
+opts.on("-n", "without numbers") { $with_nums = false }
+opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) }
+files = opts.parse(ARGV)
+
+if $with_nums
+ $obj = {
+ 'a' => 'Alpha',
+ 'b' => true,
+ 'c' => 12345,
+ 'd' => [ true, [false, [12345, nil], 3.967, ['something', false], nil]],
+ 'e' => { 'one' => 1, 'two' => 2 },
+ 'f' => nil,
+ }
+ $obj['g'] = Jazz.new() if $with_object
+ $obj['h'] = 12345678901234567890123456789 if $with_bignum
+else
+ $obj = {
+ 'a' => 'Alpha',
+ 'b' => true,
+ 'c' => '12345',
+ 'd' => [ true, [false, ['12345', nil], '3.967', ['something', false], nil]],
+ 'e' => { 'one' => '1', 'two' => '2' },
+ 'f' => nil,
+ }
+end
+
+Oj.default_options = { :indent => $indent, :mode => :compat }
+Ox.default_options = { :indent => $indent, :mode => :object }
+
+$json = Oj.dump($obj)
+$xml = Ox.dump($obj, :indent => $indent)
+begin
+ $msgpack = MessagePack.pack($obj)
+rescue Exception => e
+ puts "MessagePack failed to pack! #{e.class}: #{e.message}.\nSkipping."
+ $msgpack = nil
+end
+
+puts '-' * 80
+puts "Load/Parse Performance"
+perf = Perf.new()
+perf.add('Oj', 'load') { Oj.load($json) }
+perf.add('Yajl', 'parse') { Yajl::Parser.parse($json) }
+perf.add('JSON::Ext', 'parse') { JSON::Ext::Parser.new($json).parse }
+perf.add('JSON::Pure', 'parse') { JSON::Pure::Parser.new($json).parse }
+perf.add('Ox', 'load') { Ox.load($xml) }
+perf.add('MessagePack', 'unpack') { MessagePack.unpack($msgpack) } unless $msgpack.nil?
+perf.run($iter)
+
+puts
+puts '-' * 80
+puts "Dump/Encode/Generate Performance"
+perf = Perf.new()
+perf.add('Oj', 'dump') { Oj.dump($obj) }
+perf.add('Yajl', 'encode') { Yajl::Encoder.encode($obj) }
+if 0 == $indent
+ perf.add('JSON::Ext', 'generate') { JSON.generate($obj) }
+else
+ perf.add('JSON::Ext', 'generate') { JSON.pretty_generate($obj) }
+end
+perf.before('JSON::Ext') { JSON.generator = JSON::Ext::Generator }
+if 0 == $indent
+ perf.add('JSON::Pure', 'generate') { JSON.generate($obj) }
+else
+ perf.add('JSON::Pure', 'generate') { JSON.pretty_generate($obj) }
+end
+perf.before('JSON::Pure') { JSON.generator = JSON::Pure::Generator }
+perf.add('Ox', 'dump') { Ox.dump($obj) }
+perf.add('MessagePack', 'pack') { MessagePack.pack($obj) } unless $msgpack.nil?
+perf.run($iter)
+
+puts
View
234 test/simple.rb
@@ -1,234 +0,0 @@
-#!/usr/bin/env ruby -wW1
-# encoding: UTF-8
-
-$: << File.join(File.dirname(__FILE__), "../lib")
-$: << File.join(File.dirname(__FILE__), "../ext")
-
-require 'test/unit'
-require 'oj'
-
-class Jeez
- def initialize(x, y)
- @x = x
- @y = y
- end
-
- def to_json()
- %{{"x":#{@x},"y":#{@y}}}
- end
-
-end # Jeez
-
-class Jazz
- attr_reader :x, :y
-
- def initialize(x, y)
- @x = x
- @y = y
- end
- def to_hash()
- { 'x' => @x, 'y' => @y }
- end
-
-end # Jazz
-
-class Juice < ::Test::Unit::TestCase
-
- def test_get_options
- opts = Oj.default_options()
- assert_equal(opts, {
- :encoding=>nil,
- :indent=>0,
- :circular=>false,
- :mode=>:object})
- end
-
- def test_set_options
- orig = {
- :encoding=>nil,
- :indent=>0,
- :circular=>false,
- :mode=>:object}
- o2 = {
- :encoding=>"UTF-8",
- :indent=>4,
- :circular=>true,
- :mode=>:compat}
- o3 = { :indent => 4 }
- Oj.default_options = o2
- opts = Oj.default_options()
- assert_equal(opts, o2);
- Oj.default_options = o3 # see if it throws an exception
- Oj.default_options = orig # return to original
- end
-
- def test_nil
- dump_and_load(nil, false)
- end
-
- def test_true
- dump_and_load(true, false)
- end
-
- def test_false
- dump_and_load(false, false)
- end
-
- def test_fixnum
- dump_and_load(0, false)
- dump_and_load(12345, false)
- dump_and_load(-54321, false)
- dump_and_load(1, false)
- end
-
- def test_bignum
- dump_and_load(12345678901234567890123456789, false)
- end
-
- def test_float
- dump_and_load(0.0, false)
- dump_and_load(12345.6789, false)
- dump_and_load(-54321.012, false)
- dump_and_load(2.48e16, false)
- end
-
- def test_string
- dump_and_load('', false)
- dump_and_load('abc', false)
- dump_and_load("abc\ndef", false)
- dump_and_load("a\u0041", false)
- end
-
- def test_class
- begin
- json = Oj.dump(self.class, :mode => :strict)
- rescue Exception => e
- assert(true)
- end
- json = Oj.dump(self.class, :mode => :object)
- assert_equal('{"*":"Class","-":"Juice"}', json)
- json = Oj.dump(self.class, :mode => :null)
- assert_equal('null', json)
- end
-
- def test_array
- dump_and_load([], false)
- dump_and_load([true, false], false)
- dump_and_load(['a', 1, nil], false)
- dump_and_load([[nil]], false)
- dump_and_load([[nil], 58], false)
- end
-
- def test_hash
- dump_and_load({}, false)
- dump_and_load({ 'true' => true, 'false' => false}, false)
- dump_and_load({ 'true' => true, 'array' => [], 'hash' => { }}, false)
- end
-
- def test_non_str_hash
- json = Oj.dump({ 1 => true, 0 => false }, :mode => :compat)
- h = Oj.load(json, :mode => :strict)
- assert_equal({"#1" => [1, true], "#2" => [0, false]}, h)
- h = Oj.load(json, :mode => :compat)
- assert_equal({ 1 => true, 0 => false }, h)
- end
-
- def test_mixed_hash
- json = Oj.dump({ 1 => true, 0 => false, 'nil' => nil }, :mode => :compat)
- h = Oj.load(json, :mode => :strict)
- assert_equal({"#1" => [1, true], "#2" => [0, false], 'nil' => nil}, h)
- h = Oj.load(json, :mode => :object)
- assert_equal({ 1 => true, 0 => false, 'nil' => nil }, h)
- end
-
- def test_encode
- Oj.default_options = { :encoding => 'UTF-8' }
- dump_and_load("ぴーたー", false)
- Oj.default_options = { :encoding => nil }
- end
-
- def test_time
- t = Time.new(2012, 1, 5, 23, 58, 7)
- begin
- json = Oj.dump(t, :mode => :strict)
- rescue Exception => e
- assert(true)
- end
- json = Oj.dump(t, :mode => :compat)
- assert_equal(%{{"*":"Time","-":1325775487.000000}}, json)
- json = Oj.dump(t, :mode => :null)
- assert_equal('null', json)
- end
-
- def test_json_object
- begin
- json = Oj.dump(Jeez.new(true, 58), :mode => :strict)
- rescue Exception => e
- assert(true)
- end
- json = Oj.dump(Jeez.new(true, 58), :mode => :compat, :indent => 2)
- assert_equal(%{{"x":true,"y":58}}, json)
- json = Oj.dump(Jeez.new(true, 58), :mode => :null)
- assert_equal('null', json)
- end
-
- def test_hash_object
- obj = Jazz.new(true, 58)
- begin
- json = Oj.dump(obj, :mode => :strict)
- rescue Exception => e
- assert(true)
- end
- json = Oj.dump(obj, :mode => :compat, :indent => 2)
- assert_equal(%{{
- "x":true,
- "y":58}}, json)
- json = Oj.dump(obj, :mode => :null)
- assert_equal('null', json)
- json = Oj.dump(obj, :mode => :object, :indent => 2)
- assert_equal(%{{
- "*":"Jazz",
- "x":true,
- "y":58}}, json)
- obj2 = Oj.load(json, :mode => :object)
- puts "*** x: #{obj2.x}, y: #{obj2.y}"
- end
-
- def test_object
- begin
- json = Oj.dump(Jazz.new(true, 58), :mode => :strict)
- rescue Exception => e
- assert(true)
- end
- json = Oj.dump(Jazz.new(true, 58), :mode => :object, :indent => 2)
- assert_equal(%{{
- "*":"Jazz",
- "x":true,
- "y":58}}, json)
- json = Oj.dump(Jazz.new(true, 58), :mode => :null)
- assert_equal('null', json)
- end
-
- def test_exception
- err = nil
- begin
- raise StandardError.new('A Message')
- rescue Exception => e
- err = e
- end
- json = Oj.dump(err, :mode => :object, :indent => 2)
- e2 = Oj.load(json, :mode => :strict)
- assert_equal(err.class.to_s, e2['*'])
- assert_equal(err.message, e2['mesg'])
- assert_equal(err.backtrace, e2['bt'])
- end
-
- def dump_and_load(obj, trace=false)
- json = Oj.dump(obj, :indent => 2)
- puts json if trace
- loaded = Oj.load(json);
- assert_equal(obj, loaded)
- loaded
- end
-
-end
View
359 test/tests.rb
@@ -0,0 +1,359 @@
+#!/usr/bin/env ruby -wW1
+# encoding: UTF-8
+
+$: << File.join(File.dirname(__FILE__), "../lib")
+$: << File.join(File.dirname(__FILE__), "../ext")
+
+require 'test/unit'
+require 'oj'
+
+class Jam
+ attr_reader :x, :y
+
+ def initialize(x, y)
+ @x = x
+ @y = y
+ end
+
+ def eql?(o)
+ self.class == o.class && @x == o.x && @y == o.y
+ end
+ alias == eql?
+
+end # Jam
+
+class Jeez < Jam
+ def initialize(x, y)
+ super
+ end
+
+ def to_json()
+ %{{"x":#{@x},"y":#{@y}}}
+ end
+
+end # Jeez
+
+class Jazz < Jam
+ def initialize(x, y)
+ super
+ end
+ def to_hash()
+ { 'x' => @x, 'y' => @y }
+ end
+end # Jazz
+
+class Juice < ::Test::Unit::TestCase
+
+ def test_get_options
+ opts = Oj.default_options()
+ assert_equal(opts, {
+ :encoding=>nil,
+ :indent=>0,
+ :circular=>false,
+ :mode=>:object})
+ end
+
+ def test_set_options
+ orig = {
+ :encoding=>nil,
+ :indent=>0,
+ :circular=>false,
+ :mode=>:object}
+ o2 = {
+ :encoding=>"UTF-8",
+ :indent=>4,
+ :circular=>true,
+ :mode=>:compat}
+ o3 = { :indent => 4 }
+ Oj.default_options = o2
+ opts = Oj.default_options()
+ assert_equal(opts, o2);
+ Oj.default_options = o3 # see if it throws an exception
+ Oj.default_options = orig # return to original
+ end
+
+ def test_nil
+ dump_and_load(nil, false)
+ end
+
+ def test_true
+ dump_and_load(true, false)
+ end
+
+ def test_false
+ dump_and_load(false, false)
+ end
+
+ def test_fixnum
+ dump_and_load(0, false)
+ dump_and_load(12345, false)
+ dump_and_load(-54321, false)
+ dump_and_load(1, false)
+ end
+
+ def test_bignum
+ dump_and_load(12345678901234567890123456789, false)
+ end
+
+ def test_float
+ dump_and_load(0.0, false)
+ dump_and_load(12345.6789, false)
+ dump_and_load(-54321.012, false)
+ dump_and_load(2.48e16, false)
+ end
+
+ def test_string
+ dump_and_load('', false)
+ dump_and_load('abc', false)
+ dump_and_load("abc\ndef", false)
+ dump_and_load("a\u0041", false)
+ end
+
+ def test_encode
+ Oj.default_options = { :encoding => 'UTF-8' }
+ dump_and_load("ぴーたー", false)
+ Oj.default_options = { :encoding => nil }
+ end
+
+ def test_array
+ dump_and_load([], false)
+ dump_and_load([true, false], false)
+ dump_and_load(['a', 1, nil], false)
+ dump_and_load([[nil]], false)
+ dump_and_load([[nil], 58], false)
+ end
+
+ # Symbol
+ def test_symbol_strict
+ begin
+ json = Oj.dump(:abc, :mode => :strict)
+ rescue Exception => e
+ assert(true)
+ end
+ end
+ def test_symbol_null
+ json = Oj.dump(:abc, :mode => :null)
+ assert_equal('null', json)
+ end
+ def test_symbol_compat
+ json = Oj.dump(:abc, :mode => :compat)
+ assert_equal('"abc"', json)
+ end
+ def test_symbol_object
+ Oj.default_options = { :mode => :object }
+ dump_and_load(''.to_sym, false)
+ dump_and_load(:abc, false)
+ dump_and_load(':xyz'.to_sym, false)
+ end
+
+ # Time
+ def test_time_strict
+ t = Time.new(2012, 1, 5, 23, 58, 7)
+ begin
+ json = Oj.dump(t, :mode => :strict)
+ rescue Exception => e
+ assert(true)
+ end
+ end
+ def test_time_null
+ t = Time.new(2012, 1, 5, 23, 58, 7)
+ json = Oj.dump(t, :mode => :null)
+ assert_equal('null', json)
+ end
+ def test_time_compat
+ t = Time.new(2012, 1, 5, 23, 58, 7)
+ json = Oj.dump(t, :mode => :compat)
+ assert_equal(%{1325775487.000000}, json)
+ end
+ def test_time_object
+ t = Time.now()
+ Oj.default_options = { :mode => :object }
+ dump_and_load(t, false)
+ end
+
+# Class
+ def test_class_strict
+ begin
+ json = Oj.dump(Juice, :mode => :strict)
+ rescue Exception => e
+ assert(true)
+ end
+ end
+ def test_class_null
+ json = Oj.dump(Juice, :mode => :null)
+ assert_equal('null', json)
+ end
+ def test_class_compat
+ json = Oj.dump(Juice, :mode => :compat)
+ assert_equal(%{"Juice"}, json)
+ end
+ def test_class_object
+ Oj.default_options = { :mode => :object }
+ dump_and_load(Juice, false)
+ end
+
+# Hash
+ def test_hash
+ Oj.default_options = { :mode => :strict }
+ dump_and_load({}, false)
+ dump_and_load({ 'true' => true, 'false' => false}, false)
+ dump_and_load({ 'true' => true, 'array' => [], 'hash' => { }}, false)
+ end
+ def test_non_str_hash_strict
+ begin
+ json = Oj.dump({ 1 => true, 0 => false }, :mode => :strict)
+ rescue Exception => e
+ assert(true)
+ end
+ end
+ def test_non_str_hash_null
+ begin
+ json = Oj.dump({ 1 => true, 0 => false }, :mode => :null)
+ rescue Exception => e
+ assert(true)
+ end
+ end
+ def test_non_str_hash_compat
+ begin
+ json = Oj.dump({ 1 => true, 0 => false }, :mode => :compat)
+ rescue Exception => e
+ assert(true)
+ end
+ end
+ def test_non_str_hash_object
+ Oj.default_options = { :mode => :object }
+ json = Oj.dump({ 1 => true, 0 => false, :sim => nil })
+ h = Oj.load(json, :mode => :strict)
+ assert_equal({"^#1" => [1, true], "^#2" => [0, false], ":sim" => nil}, h)
+ h = Oj.load(json)
+ assert_equal({ 1 => true, 0 => false, :sim => nil }, h)
+ end
+ def test_mixed_hash_object
+ Oj.default_options = { :mode => :object }
+ json = Oj.dump({ 1 => true, 0 => false, 'nil' => nil, :sim => 4 })
+ h = Oj.load(json, :mode => :strict)
+ assert_equal({"^#1" => [1, true], "^#2" => [0, false], "nil" => nil, ":sim" => 4}, h)
+ h = Oj.load(json)
+ assert_equal({ 1 => true, 0 => false, 'nil' => nil, :sim => 4 }, h)
+ end
+
+# Object with to_json()
+ def test_json_object_strict
+ obj = Jeez.new(true, 58)
+ begin
+ json = Oj.dump(obj, :mode => :strict)
+ rescue Exception => e
+ assert(true)
+ end
+ end
+ def test_json_object_null
+ obj = Jeez.new(true, 58)
+ json = Oj.dump(obj, :mode => :null)
+ assert_equal('null', json)
+ end
+ def test_json_object_compat
+ obj = Jeez.new(true, 58)
+ json = Oj.dump(obj, :mode => :compat, :indent => 2)
+ assert_equal(%{{"x":true,"y":58}}, json)
+ end
+ def test_json_object_object
+ obj = Jeez.new(true, 58)
+ json = Oj.dump(obj, :mode => :object, :indent => 2)
+ assert_equal(%{{
+ "^o":"Jeez",
+ "x":true,
+ "y":58}}, json)
+ obj2 = Oj.load(json, :mode => :object)
+ assert_equal(obj, obj2)
+ end
+
+# Object with to_hash()
+ def test_to_hash_object_strict
+ obj = Jazz.new(true, 58)
+ begin
+ json = Oj.dump(obj, :mode => :strict)
+ rescue Exception => e
+ assert(true)
+ end
+ end
+ def test_to_hash_object_null
+ obj = Jazz.new(true, 58)
+ json = Oj.dump(obj, :mode => :null)
+ assert_equal('null', json)
+ end
+ def test_to_hash_object_compat
+ obj = Jazz.new(true, 58)
+ json = Oj.dump(obj, :mode => :compat, :indent => 2)
+ assert_equal(%{{
+ "x":true,
+ "y":58}}, json)
+ end
+ def test_to_hash_object_object
+ obj = Jazz.new(true, 58)
+ json = Oj.dump(obj, :mode => :object, :indent => 2)
+ assert_equal(%{{
+ "^o":"Jazz",
+ "x":true,
+ "y":58}}, json)
+ obj2 = Oj.load(json, :mode => :object)
+ assert_equal(obj, obj2)
+ end
+
+# Object without to_json() or to_hash()
+ def test_object_strict
+ obj = Jam.new(true, 58)
+ begin
+ json = Oj.dump(obj, :mode => :strict)
+ rescue Exception => e
+ assert(true)
+ end
+ end
+ def test_object_null
+ obj = Jam.new(true, 58)
+ json = Oj.dump(obj, :mode => :null)
+ assert_equal('null', json)
+ end
+ def test_object_compat
+ obj = Jam.new(true, 58)
+ json = Oj.dump(obj, :mode => :compat, :indent => 2)
+ assert_equal(%{{
+ "x":true,
+ "y":58}}, json)
+ end
+ def test_object_object
+ obj = Jam.new(true, 58)
+ json = Oj.dump(obj, :mode => :object, :indent => 2)
+ assert_equal(%{{
+ "^o":"Jam",
+ "x":true,
+ "y":58}}, json)
+ obj2 = Oj.load(json, :mode => :object)
+ assert_equal(obj, obj2)
+ end
+
+ def test_exception
+ err = nil
+ begin
+ raise StandardError.new('A Message')
+ rescue Exception => e
+ err = e
+ end
+ json = Oj.dump(err, :mode => :object, :indent => 2)
+ #puts "*** #{json}"
+ e2 = Oj.load(json, :mode => :strict)
+ assert_equal(err.class.to_s, e2['^o'])
+ assert_equal(err.message, e2['~mesg'])
+ assert_equal(err.backtrace, e2['~bt'])
+ e2 = Oj.load(json, :mode => :object)
+ assert_equal(e, e2);
+ end
+
+ def dump_and_load(obj, trace=false)
+ json = Oj.dump(obj, :indent => 2)
+ puts json if trace
+ loaded = Oj.load(json);
+ assert_equal(obj, loaded)
+ loaded
+ end
+
+end
Please sign in to comment.
Something went wrong with that request. Please try again.