/* custom.c * Copyright (c) 2012, 2017, Peter Ohler * All rights reserved. */ #include #include #include "code.h" #include "dump.h" #include "encode.h" #include "err.h" #include "hash.h" #include "odd.h" #include "oj.h" #include "parse.h" #include "resolve.h" #include "trace.h" #include "util.h" extern void oj_set_obj_ivar(Val parent, Val kval, VALUE value); extern VALUE oj_parse_xml_time(const char *str, int len); // from object.c static void dump_obj_str(VALUE obj, int depth, Out out) { struct _attr attrs[] = { { "s", 1, Qnil }, { NULL, 0, Qnil }, }; attrs->value = rb_funcall(obj, oj_to_s_id, 0); oj_code_attrs(obj, attrs, depth, out, Yes == out->opts->create_ok); } static void dump_obj_as_str(VALUE obj, int depth, Out out) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); const char *str = rb_string_value_ptr((VALUE*)&rstr); oj_dump_cstr(str, RSTRING_LEN(rstr), 0, 0, out); } static void bigdecimal_dump(VALUE obj, int depth, Out out) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); const char *str = rb_string_value_ptr((VALUE*)&rstr); int len = (int)RSTRING_LEN(rstr); if (0 == strcasecmp("Infinity", str)) { str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, true, &len); oj_dump_raw(str, len, out); } else if (0 == strcasecmp("-Infinity", str)) { str = oj_nan_str(obj, out->opts->dump_opts.nan_dump, out->opts->mode, false, &len); oj_dump_raw(str, len, out); } else if (No == out->opts->bigdec_as_num) { oj_dump_cstr(str, len, 0, 0, out); } else { oj_dump_raw(str, len, out); } } static ID real_id = 0; static ID imag_id = 0; static void complex_dump(VALUE obj, int depth, Out out) { if (NULL != out->opts->create_id) { struct _attr attrs[] = { { "real", 4, Qnil }, { "imag", 4, Qnil }, { NULL, 0, Qnil }, }; if (0 == real_id) { real_id = rb_intern("real"); imag_id = rb_intern("imag"); } attrs[0].value = rb_funcall(obj, real_id, 0); attrs[1].value = rb_funcall(obj, imag_id, 0); oj_code_attrs(obj, attrs, depth, out, Yes == out->opts->create_ok); } else { dump_obj_as_str(obj, depth, out); } } static VALUE complex_load(VALUE clas, VALUE args) { if (0 == real_id) { real_id = rb_intern("real"); imag_id = rb_intern("imag"); } return rb_complex_new(rb_hash_aref(args, rb_id2str(real_id)), rb_hash_aref(args, rb_id2str(imag_id))); } static void time_dump(VALUE obj, int depth, Out out) { if (Yes == out->opts->create_ok) { struct _attr attrs[] = { { "time", 4, Qundef, 0, Qundef }, { NULL, 0, Qnil }, }; attrs->time = obj; oj_code_attrs(obj, attrs, depth, out, true); } else { switch (out->opts->time_format) { case RubyTime: oj_dump_ruby_time(obj, out); break; case XmlTime: oj_dump_xml_time(obj, out); break; case UnixZTime: oj_dump_time(obj, out, true); break; case UnixTime: default: oj_dump_time(obj, out, false); break; } } } static void date_dump(VALUE obj, int depth, Out out) { if (Yes == out->opts->create_ok) { struct _attr attrs[] = { { "s", 1, Qnil }, { NULL, 0, Qnil }, }; attrs->value = rb_funcall(obj, rb_intern("iso8601"), 0); oj_code_attrs(obj, attrs, depth, out, Yes == out->opts->create_ok); } else { volatile VALUE v; volatile VALUE ov; switch (out->opts->time_format) { case RubyTime: case XmlTime: v = rb_funcall(obj, rb_intern("iso8601"), 0); oj_dump_cstr(rb_string_value_ptr((VALUE*)&v), (int)RSTRING_LEN(v), 0, 0, out); break; case UnixZTime: v = rb_funcall(obj, rb_intern("to_time"), 0); if (oj_date_class == rb_obj_class(obj)) { ov = rb_funcall(v, rb_intern("utc_offset"), 0); v = rb_funcall(v, rb_intern("utc"), 0); v = rb_funcall(v, rb_intern("+"), 1, ov); oj_dump_time(v, out, false); } else { oj_dump_time(v, out, true); } break; case UnixTime: default: v = rb_funcall(obj, rb_intern("to_time"), 0); if (oj_date_class == rb_obj_class(obj)) { ov = rb_funcall(v, rb_intern("utc_offset"), 0); v = rb_funcall(v, rb_intern("utc"), 0); v = rb_funcall(v, rb_intern("+"), 1, ov); } oj_dump_time(v, out, false); break; } } } static VALUE date_load(VALUE clas, VALUE args) { volatile VALUE v; if (Qnil != (v = rb_hash_aref(args, rb_str_new2("s")))) { return rb_funcall(oj_date_class, rb_intern("parse"), 1, v); } return Qnil; } static VALUE datetime_load(VALUE clas, VALUE args) { volatile VALUE v; if (Qnil != (v = rb_hash_aref(args, rb_str_new2("s")))) { return rb_funcall(oj_datetime_class, rb_intern("parse"), 1, v); } return Qnil; } static ID table_id = 0; static void openstruct_dump(VALUE obj, int depth, Out out) { struct _attr attrs[] = { { "table", 5, Qnil }, { NULL, 0, Qnil }, }; if (0 == table_id) { table_id = rb_intern("table"); } attrs->value = rb_funcall(obj, table_id, 0); oj_code_attrs(obj, attrs, depth, out, Yes == out->opts->create_ok); } static VALUE openstruct_load(VALUE clas, VALUE args) { if (0 == table_id) { table_id = rb_intern("table"); } return rb_funcall(clas, oj_new_id, 1, rb_hash_aref(args, rb_id2str(table_id))); } static void range_dump(VALUE obj, int depth, Out out) { if (NULL != out->opts->create_id) { struct _attr attrs[] = { { "begin", 5, Qnil }, { "end", 3, Qnil }, { "exclude", 7, Qnil }, { NULL, 0, Qnil }, }; attrs[0].value = rb_funcall(obj, oj_begin_id, 0); attrs[1].value = rb_funcall(obj, oj_end_id, 0); attrs[2].value = rb_funcall(obj, oj_exclude_end_id, 0); oj_code_attrs(obj, attrs, depth, out, Yes == out->opts->create_ok); } else { dump_obj_as_str(obj, depth, out); } } static VALUE range_load(VALUE clas, VALUE args) { VALUE nargs[3]; nargs[0] = rb_hash_aref(args, rb_id2str(oj_begin_id)); nargs[1] = rb_hash_aref(args, rb_id2str(oj_end_id)); nargs[2] = rb_hash_aref(args, rb_id2str(oj_exclude_end_id)); return rb_class_new_instance(3, nargs, rb_cRange); } static ID numerator_id = 0; static ID denominator_id = 0; static void rational_dump(VALUE obj, int depth, Out out) { if (NULL != out->opts->create_id) { struct _attr attrs[] = { { "numerator", 9, Qnil }, { "denominator", 11, Qnil }, { NULL, 0, Qnil }, }; if (0 == numerator_id) { numerator_id = rb_intern("numerator"); denominator_id = rb_intern("denominator"); } attrs[0].value = rb_funcall(obj, numerator_id, 0); attrs[1].value = rb_funcall(obj, denominator_id, 0); oj_code_attrs(obj, attrs, depth, out, Yes == out->opts->create_ok); } else { dump_obj_as_str(obj, depth, out); } } static VALUE rational_load(VALUE clas, VALUE args) { if (0 == numerator_id) { numerator_id = rb_intern("numerator"); denominator_id = rb_intern("denominator"); } return rb_rational_new(rb_hash_aref(args, rb_id2str(numerator_id)), rb_hash_aref(args, rb_id2str(denominator_id))); } static VALUE regexp_load(VALUE clas, VALUE args) { volatile VALUE v; if (Qnil != (v = rb_hash_aref(args, rb_str_new2("s")))) { return rb_funcall(rb_cRegexp, oj_new_id, 1, v); } return Qnil; } static VALUE time_load(VALUE clas, VALUE args) { // Value should have already been replaced in one of the hash_set_xxx // functions. return args; } static struct _code codes[] = { { "BigDecimal", Qnil, bigdecimal_dump, NULL, true }, { "Complex", Qnil, complex_dump, complex_load, true }, { "Date", Qnil, date_dump, date_load, true }, { "DateTime", Qnil, date_dump, datetime_load, true }, { "OpenStruct", Qnil, openstruct_dump, openstruct_load, true }, { "Range", Qnil, range_dump, range_load, true }, { "Rational", Qnil, rational_dump, rational_load, true }, { "Regexp", Qnil, dump_obj_str, regexp_load, true }, { "Time", Qnil, time_dump, time_load, true }, { NULL, Qundef, NULL, NULL, false }, }; static int hash_cb(VALUE key, VALUE value, VALUE ov) { Out out = (Out)ov; int depth = out->depth; if (oj_dump_ignore(out->opts, value)) { return ST_CONTINUE; } if (out->omit_nil && Qnil == value) { return ST_CONTINUE; } if (!out->opts->dump_opts.use) { assure_size(out, depth * out->indent + 1); fill_indent(out, depth); } else { assure_size(out, depth * out->opts->dump_opts.indent_size + out->opts->dump_opts.hash_size + 1); if (0 < out->opts->dump_opts.hash_size) { strcpy(out->cur, out->opts->dump_opts.hash_nl); out->cur += out->opts->dump_opts.hash_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } switch (rb_type(key)) { case T_STRING: oj_dump_str(key, 0, out, false); break; case T_SYMBOL: oj_dump_sym(key, 0, out, false); break; default: oj_dump_str(rb_funcall(key, oj_to_s_id, 0), 0, out, false); break; } if (!out->opts->dump_opts.use) { *out->cur++ = ':'; } else { assure_size(out, out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2); if (0 < out->opts->dump_opts.before_size) { strcpy(out->cur, out->opts->dump_opts.before_sep); out->cur += out->opts->dump_opts.before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts.after_size) { strcpy(out->cur, out->opts->dump_opts.after_sep); out->cur += out->opts->dump_opts.after_size; } } oj_dump_custom_val(value, depth, out, true); out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } static void dump_hash(VALUE obj, int depth, Out out, bool as_ok) { int cnt; long id = oj_check_circular(obj, out); if (0 > id) { oj_dump_nil(Qnil, depth, out, false); return; } cnt = (int)RHASH_SIZE(obj); assure_size(out, 2); if (0 == cnt) { *out->cur++ = '{'; *out->cur++ = '}'; } else { *out->cur++ = '{'; out->depth = depth + 1; rb_hash_foreach(obj, hash_cb, (VALUE)out); if (',' == *(out->cur - 1)) { out->cur--; // backup to overwrite last comma } if (!out->opts->dump_opts.use) { assure_size(out, depth * out->indent + 2); fill_indent(out, depth); } else { assure_size(out, depth * out->opts->dump_opts.indent_size + out->opts->dump_opts.hash_size + 1); if (0 < out->opts->dump_opts.hash_size) { strcpy(out->cur, out->opts->dump_opts.hash_nl); out->cur += out->opts->dump_opts.hash_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } *out->cur++ = '}'; } *out->cur = '\0'; } static void dump_odd(VALUE obj, Odd odd, VALUE clas, int depth, Out out) { ID *idp; AttrGetFunc *fp; volatile VALUE v; const char *name; size_t size; int d2 = depth + 1; assure_size(out, 2); *out->cur++ = '{'; if (NULL != out->opts->create_id && Yes == out->opts->create_ok) { const char *classname = rb_class2name(clas); int clen = (int)strlen(classname); size_t sep_len = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2; size = d2 * out->indent + 10 + clen + out->opts->create_id_len + sep_len; assure_size(out, size); fill_indent(out, d2); *out->cur++ = '"'; memcpy(out->cur, out->opts->create_id, out->opts->create_id_len); out->cur += out->opts->create_id_len; *out->cur++ = '"'; if (0 < out->opts->dump_opts.before_size) { strcpy(out->cur, out->opts->dump_opts.before_sep); out->cur += out->opts->dump_opts.before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts.after_size) { strcpy(out->cur, out->opts->dump_opts.after_sep); out->cur += out->opts->dump_opts.after_size; } *out->cur++ = '"'; memcpy(out->cur, classname, clen); out->cur += clen; *out->cur++ = '"'; *out->cur++ = ','; } if (odd->raw) { v = rb_funcall(obj, *odd->attrs, 0); if (Qundef == v || T_STRING != rb_type(v)) { rb_raise(rb_eEncodingError, "Invalid type for raw JSON.\n"); } else { const char *s = rb_string_value_ptr((VALUE*)&v); int len = (int)RSTRING_LEN(v); const char *name = rb_id2name(*odd->attrs); size_t nlen = strlen(name); size = len + d2 * out->indent + nlen + 10; assure_size(out, size); fill_indent(out, d2); *out->cur++ = '"'; memcpy(out->cur, name, nlen); out->cur += nlen; *out->cur++ = '"'; *out->cur++ = ':'; memcpy(out->cur, s, len); out->cur += len; *out->cur = '\0'; } } else { size = d2 * out->indent + 1; for (idp = odd->attrs, fp = odd->attrFuncs; 0 != *idp; idp++, fp++) { size_t nlen; assure_size(out, size); name = rb_id2name(*idp); nlen = strlen(name); if (0 != *fp) { v = (*fp)(obj); } else if (0 == strchr(name, '.')) { v = rb_funcall(obj, *idp, 0); } else { char nbuf[256]; char *n2 = nbuf; char *n; char *end; ID i; if (sizeof(nbuf) <= nlen) { if (NULL == (n2 = strdup(name))) { rb_raise(rb_eNoMemError, "for attribute name."); } } else { strcpy(n2, name); } n = n2; v = obj; while (0 != (end = strchr(n, '.'))) { *end = '\0'; i = rb_intern(n); v = rb_funcall(v, i, 0); n = end + 1; } i = rb_intern(n); v = rb_funcall(v, i, 0); if (nbuf != n2) { free(n2); } } fill_indent(out, d2); oj_dump_cstr(name, nlen, 0, 0, out); *out->cur++ = ':'; oj_dump_custom_val(v, d2, out, true); assure_size(out, 2); *out->cur++ = ','; } out->cur--; } *out->cur++ = '}'; *out->cur = '\0'; } // Return class if still needs dumping. static VALUE dump_common(VALUE obj, int depth, Out out) { if (Yes == out->opts->raw_json && rb_respond_to(obj, oj_raw_json_id)) { oj_dump_raw_json(obj, depth, out); } else if (Yes == out->opts->to_json && rb_respond_to(obj, oj_to_json_id)) { volatile VALUE rs; const char *s; int len; if (Yes == out->opts->trace) { oj_trace("to_json", obj, __FILE__, __LINE__, depth + 1, TraceRubyIn); } if (0 == rb_obj_method_arity(obj, oj_to_json_id)) { rs = rb_funcall(obj, oj_to_json_id, 0); } else { rs = rb_funcall2(obj, oj_to_json_id, out->argc, out->argv); } if (Yes == out->opts->trace) { oj_trace("to_json", obj, __FILE__, __LINE__, depth + 1, TraceRubyOut); } s = rb_string_value_ptr((VALUE*)&rs); len = (int)RSTRING_LEN(rs); assure_size(out, len + 1); memcpy(out->cur, s, len); out->cur += len; *out->cur = '\0'; } else if (Yes == out->opts->as_json && rb_respond_to(obj, oj_as_json_id)) { volatile VALUE aj; if (Yes == out->opts->trace) { oj_trace("as_json", obj, __FILE__, __LINE__, depth + 1, TraceRubyIn); } // Some classes elect to not take an options argument so check the arity // of as_json. if (0 == rb_obj_method_arity(obj, oj_as_json_id)) { aj = rb_funcall(obj, oj_as_json_id, 0); } else { aj = rb_funcall2(obj, oj_as_json_id, out->argc, out->argv); } if (Yes == out->opts->trace) { oj_trace("as_json", obj, __FILE__, __LINE__, depth + 1, TraceRubyOut); } // Catch the obvious brain damaged recursive dumping. if (aj == obj) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); oj_dump_cstr(rb_string_value_ptr((VALUE*)&rstr), (int)RSTRING_LEN(rstr), false, false, out); } else { oj_dump_custom_val(aj, depth, out, true); } } else if (Yes == out->opts->to_hash && rb_respond_to(obj, oj_to_hash_id)) { volatile VALUE h = rb_funcall(obj, oj_to_hash_id, 0); if (T_HASH != rb_type(h)) { // It seems that ActiveRecord implemented to_hash so that it returns // an Array and not a Hash. To get around that any value returned // will be dumped. //rb_raise(rb_eTypeError, "%s.to_hash() did not return a Hash.\n", rb_class2name(rb_obj_class(obj))); oj_dump_custom_val(h, depth, out, false); } else { dump_hash(h, depth, out, true); } } else if (!oj_code_dump(codes, obj, depth, out)) { VALUE clas = rb_obj_class(obj); Odd odd = oj_get_odd(clas); if (NULL == odd) { return clas; } dump_odd(obj, odd, clas, depth + 1, out); } return Qnil; } static int dump_attr_cb(ID key, VALUE value, VALUE ov) { Out out = (Out)ov; int depth = out->depth; size_t size; const char *attr; if (oj_dump_ignore(out->opts, value)) { return ST_CONTINUE; } if (out->omit_nil && Qnil == value) { return ST_CONTINUE; } size = depth * out->indent + 1; attr = rb_id2name(key); // Some exceptions such as NoMethodError have an invisible attribute where // the key name is NULL. Not an empty string but NULL. if (NULL == attr) { attr = ""; } else if (Yes == out->opts->ignore_under && '@' == *attr && '_' == attr[1]) { return ST_CONTINUE; } if (0 == strcmp("bt", attr) || 0 == strcmp("mesg", attr)) { return ST_CONTINUE; } assure_size(out, size); fill_indent(out, depth); if ('@' == *attr) { attr++; oj_dump_cstr(attr, strlen(attr), 0, 0, out); } else { char buf[32]; *buf = '~'; strncpy(buf + 1, attr, sizeof(buf) - 2); buf[sizeof(buf) - 1] = '\0'; oj_dump_cstr(buf, strlen(buf), 0, 0, out); } *out->cur++ = ':'; oj_dump_custom_val(value, depth, out, true); out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } static void dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out) { size_t size = 0; int d2 = depth + 1; int cnt; bool class_written = false; assure_size(out, 2); *out->cur++ = '{'; if (Qundef != clas && NULL != out->opts->create_id && Yes == out->opts->create_ok) { size_t sep_len = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2; const char *classname = rb_obj_classname(obj); size_t len = strlen(classname); size = d2 * out->indent + 10 + len + out->opts->create_id_len + sep_len; assure_size(out, size); fill_indent(out, d2); *out->cur++ = '"'; memcpy(out->cur, out->opts->create_id, out->opts->create_id_len); out->cur += out->opts->create_id_len; *out->cur++ = '"'; if (0 < out->opts->dump_opts.before_size) { strcpy(out->cur, out->opts->dump_opts.before_sep); out->cur += out->opts->dump_opts.before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts.after_size) { strcpy(out->cur, out->opts->dump_opts.after_sep); out->cur += out->opts->dump_opts.after_size; } *out->cur++ = '"'; memcpy(out->cur, classname, len); out->cur += len; *out->cur++ = '"'; class_written = true; } cnt = (int)rb_ivar_count(obj); if (class_written) { *out->cur++ = ','; } if (0 == cnt && Qundef == clas) { // Might be something special like an Enumerable. if (Qtrue == rb_obj_is_kind_of(obj, oj_enumerable_class)) { out->cur--; oj_dump_custom_val(rb_funcall(obj, rb_intern("entries"), 0), depth, out, false); return; } } out->depth = depth + 1; rb_ivar_foreach(obj, dump_attr_cb, (VALUE)out); if (',' == *(out->cur - 1)) { out->cur--; // backup to overwrite last comma } if (rb_obj_is_kind_of(obj, rb_eException)) { volatile VALUE rv; if (',' != *(out->cur - 1)) { *out->cur++ = ','; } // message assure_size(out, 2); fill_indent(out, d2); oj_dump_cstr("~mesg", 5, 0, 0, out); *out->cur++ = ':'; rv = rb_funcall2(obj, rb_intern("message"), 0, 0); oj_dump_custom_val(rv, d2, out, true); assure_size(out, size + 2); *out->cur++ = ','; // backtrace fill_indent(out, d2); oj_dump_cstr("~bt", 3, 0, 0, out); *out->cur++ = ':'; rv = rb_funcall2(obj, rb_intern("backtrace"), 0, 0); oj_dump_custom_val(rv, d2, out, true); assure_size(out, 2); } out->depth = depth; fill_indent(out, depth); *out->cur++ = '}'; *out->cur = '\0'; } static void dump_obj(VALUE obj, int depth, Out out, bool as_ok) { long id = oj_check_circular(obj, out); VALUE clas; if (0 > id) { oj_dump_nil(Qnil, depth, out, false); } else if (Qnil != (clas = dump_common(obj, depth, out))) { dump_obj_attrs(obj, clas, 0, depth, out); } *out->cur = '\0'; } static void dump_array(VALUE a, int depth, Out out, bool as_ok) { size_t size; int i, cnt; int d2 = depth + 1; long id = oj_check_circular(a, out); if (0 > id) { oj_dump_nil(Qnil, depth, out, false); return; } cnt = (int)RARRAY_LEN(a); *out->cur++ = '['; assure_size(out, 2); if (0 == cnt) { *out->cur++ = ']'; } else { if (out->opts->dump_opts.use) { size = d2 * out->opts->dump_opts.indent_size + out->opts->dump_opts.array_size + 1; } else { size = d2 * out->indent + 2; } cnt--; for (i = 0; i <= cnt; i++) { assure_size(out, size); if (out->opts->dump_opts.use) { if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = d2; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { fill_indent(out, d2); } oj_dump_custom_val(rb_ary_entry(a, i), d2, out, true); if (i < cnt) { *out->cur++ = ','; } } size = depth * out->indent + 1; assure_size(out, size); if (out->opts->dump_opts.use) { if (0 < out->opts->dump_opts.array_size) { strcpy(out->cur, out->opts->dump_opts.array_nl); out->cur += out->opts->dump_opts.array_size; } if (0 < out->opts->dump_opts.indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts.indent_str); out->cur += out->opts->dump_opts.indent_size; } } } else { fill_indent(out, depth); } *out->cur++ = ']'; } *out->cur = '\0'; } static void dump_struct(VALUE obj, int depth, Out out, bool as_ok) { long id = oj_check_circular(obj, out); VALUE clas; if (0 > id) { oj_dump_nil(Qnil, depth, out, false); } else if (Qnil != (clas = dump_common(obj, depth, out))) { VALUE ma = Qnil; VALUE v; char num_id[32]; int i; int d2 = depth + 1; int d3 = d2 + 1; size_t size = d2 * out->indent + d3 * out->indent + 3; const char *name; int cnt; size_t len; assure_size(out, size); if (clas == rb_cRange) { *out->cur++ = '"'; oj_dump_custom_val(rb_funcall(obj, oj_begin_id, 0), d3, out, false); assure_size(out, 3); *out->cur++ = '.'; *out->cur++ = '.'; if (Qtrue == rb_funcall(obj, oj_exclude_end_id, 0)) { *out->cur++ = '.'; } oj_dump_custom_val(rb_funcall(obj, oj_end_id, 0), d3, out, false); *out->cur++ = '"'; return; } *out->cur++ = '{'; fill_indent(out, d2); size = d3 * out->indent + 2; ma = rb_struct_s_members(clas); #ifdef RSTRUCT_LEN #if RSTRUCT_LEN_RETURNS_INTEGER_OBJECT cnt = (int)NUM2LONG(RSTRUCT_LEN(obj)); #else // RSTRUCT_LEN_RETURNS_INTEGER_OBJECT cnt = (int)RSTRUCT_LEN(obj); #endif // RSTRUCT_LEN_RETURNS_INTEGER_OBJECT #else // This is a bit risky as a struct in C ruby is not the same as a Struct // class in interpreted Ruby so length() may not be defined. cnt = FIX2INT(rb_funcall2(obj, oj_length_id, 0, 0)); #endif for (i = 0; i < cnt; i++) { #ifdef RSTRUCT_LEN v = RSTRUCT_GET(obj, i); #else v = rb_struct_aref(obj, INT2FIX(i)); #endif if (ma != Qnil) { volatile VALUE s = rb_sym_to_s(rb_ary_entry(ma, i)); name = rb_string_value_ptr((VALUE*)&s); len = (int)RSTRING_LEN(s); } else { len = snprintf(num_id, sizeof(num_id), "%d", i); name = num_id; } assure_size(out, size + len + 3); fill_indent(out, d3); *out->cur++ = '"'; memcpy(out->cur, name, len); out->cur += len; *out->cur++ = '"'; *out->cur++ = ':'; oj_dump_custom_val(v, d3, out, true); *out->cur++ = ','; } out->cur--; *out->cur++ = '}'; *out->cur = '\0'; } } static void dump_data(VALUE obj, int depth, Out out, bool as_ok) { long id = oj_check_circular(obj, out); VALUE clas; if (0 > id) { oj_dump_nil(Qnil, depth, out, false); } else if (Qnil != (clas = dump_common(obj, depth, out))) { dump_obj_attrs(obj, clas, id, depth, out); } } static void dump_regexp(VALUE obj, int depth, Out out, bool as_ok) { if (NULL != out->opts->create_id) { dump_obj_str(obj, depth, out); } else { dump_obj_as_str(obj, depth, out); } } static void dump_complex(VALUE obj, int depth, Out out, bool as_ok) { complex_dump(obj, depth, out); } static void dump_rational(VALUE obj, int depth, Out out, bool as_ok) { rational_dump(obj, depth, out); } static DumpFunc custom_funcs[] = { NULL, // RUBY_T_NONE = 0x00, dump_obj, // RUBY_T_OBJECT = 0x01, oj_dump_class, // RUBY_T_CLASS = 0x02, oj_dump_class, // RUBY_T_MODULE = 0x03, oj_dump_float, // RUBY_T_FLOAT = 0x04, oj_dump_str, // RUBY_T_STRING = 0x05, dump_regexp, // RUBY_T_REGEXP = 0x06, dump_array, // RUBY_T_ARRAY = 0x07, dump_hash, // RUBY_T_HASH = 0x08, dump_struct, // RUBY_T_STRUCT = 0x09, oj_dump_bignum, // RUBY_T_BIGNUM = 0x0a, NULL, // RUBY_T_FILE = 0x0b, dump_data, // RUBY_T_DATA = 0x0c, NULL, // RUBY_T_MATCH = 0x0d, dump_complex, // RUBY_T_COMPLEX = 0x0e, dump_rational, // RUBY_T_RATIONAL = 0x0f, NULL, // 0x10 oj_dump_nil, // RUBY_T_NIL = 0x11, oj_dump_true, // RUBY_T_TRUE = 0x12, oj_dump_false, // RUBY_T_FALSE = 0x13, oj_dump_sym, // RUBY_T_SYMBOL = 0x14, oj_dump_fixnum, // RUBY_T_FIXNUM = 0x15, }; void oj_dump_custom_val(VALUE obj, int depth, Out out, bool as_ok) { int type = rb_type(obj); if (Yes == out->opts->trace) { oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceIn); } if (MAX_DEPTH < depth) { rb_raise(rb_eNoMemError, "Too deeply nested.\n"); } if (0 < type && type <= RUBY_T_FIXNUM) { DumpFunc f = custom_funcs[type]; if (NULL != f) { f(obj, depth, out, true); if (Yes == out->opts->trace) { oj_trace("dump", obj, __FILE__, __LINE__, depth, TraceOut); } return; } } oj_dump_nil(Qnil, depth, out, false); if (Yes == out->opts->trace) { oj_trace("dump", Qnil, __FILE__, __LINE__, depth, TraceOut); } } ///// load functions ///// static void hash_set_cstr(ParseInfo pi, Val kval, const char *str, size_t len, const char *orig) { const char *key = kval->key; int klen = kval->klen; Val parent = stack_peek(&pi->stack); volatile VALUE rkey = kval->key_val; if (Qundef == rkey && Yes == pi->options.create_ok && NULL != pi->options.create_id && *pi->options.create_id == *key && (int)pi->options.create_id_len == klen && 0 == strncmp(pi->options.create_id, key, klen)) { parent->clas = oj_name2class(pi, str, len, false, rb_eArgError); if (2 == klen && '^' == *key && 'o' == key[1]) { if (Qundef != parent->clas) { if (!oj_code_has(codes, parent->clas, false)) { parent->val = rb_obj_alloc(parent->clas); } } } } else { volatile VALUE rstr = rb_str_new(str, len); if (Qundef == rkey) { rkey = rb_str_new(key, klen); rstr = oj_encode(rstr); rkey = oj_encode(rkey); if (Yes == pi->options.sym_key) { rkey = rb_str_intern(rkey); } } if (Yes == pi->options.create_ok && NULL != pi->options.str_rx.head) { VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, (int)len); if (Qnil != clas) { rstr = rb_funcall(clas, oj_json_create_id, 1, rstr); } } switch (rb_type(parent->val)) { case T_OBJECT: oj_set_obj_ivar(parent, kval, rstr); break; case T_HASH: if (4 == parent->klen && NULL != parent->key && rb_cTime == parent->clas && 0 == strncmp("time", parent->key, 4)) { if (Qnil == (parent->val = oj_parse_xml_time(str, (int)len))) { parent->val = rb_funcall(rb_cTime, rb_intern("parse"), 1, rb_str_new(str, len)); } } else { rb_hash_aset(parent->val, rkey, rstr); } break; default: break; } if (Yes == pi->options.trace) { oj_trace_parse_call("set_string", pi, __FILE__, __LINE__, rstr); } } } static void end_hash(struct _parseInfo *pi) { Val parent = stack_peek(&pi->stack); if (Qundef != parent->clas && parent->clas != rb_obj_class(parent->val)) { volatile VALUE obj = oj_code_load(codes, parent->clas, parent->val); if (Qnil != obj) { parent->val = obj; } else { parent->val = rb_funcall(parent->clas, oj_json_create_id, 1, parent->val); } parent->clas = Qundef; } if (Yes == pi->options.trace) { oj_trace_parse_hash_end(pi, __FILE__, __LINE__); } } static VALUE calc_hash_key(ParseInfo pi, Val parent) { volatile VALUE rkey = parent->key_val; if (Qundef == rkey) { rkey = rb_str_new(parent->key, parent->klen); } rkey = oj_encode(rkey); if (Yes == pi->options.sym_key) { rkey = rb_str_intern(rkey); } return rkey; } static void hash_set_num(struct _parseInfo *pi, Val kval, NumInfo ni) { Val parent = stack_peek(&pi->stack); volatile VALUE rval = oj_num_as_value(ni); switch (rb_type(parent->val)) { case T_OBJECT: oj_set_obj_ivar(parent, kval, rval); break; case T_HASH: if (4 == parent->klen && NULL != parent->key && rb_cTime == parent->clas && 0 != ni->div && 0 == strncmp("time", parent->key, 4)) { int64_t nsec = ni->num * 1000000000LL / ni->div; if (ni->neg) { ni->i = -ni->i; if (0 < nsec) { ni->i--; nsec = 1000000000LL - nsec; } } if (86400 == ni->exp) { // UTC time parent->val = rb_time_nano_new(ni->i, (long)nsec); // Since the ruby C routines alway create local time, the // offset and then a conversion to UTC keeps makes the time // match the expected value. parent->val = rb_funcall2(parent->val, oj_utc_id, 0, 0); } else if (ni->has_exp) { int64_t t = (int64_t)(ni->i + ni->exp); struct _timeInfo ti; VALUE args[8]; sec_as_time(t, &ti); args[0] = LONG2NUM(ti.year); args[1] = LONG2NUM(ti.mon); args[2] = LONG2NUM(ti.day); args[3] = LONG2NUM(ti.hour); args[4] = LONG2NUM(ti.min); args[5] = rb_float_new((double)ti.sec + ((double)nsec + 0.5) / 1000000000.0); args[6] = LONG2NUM(ni->exp); parent->val = rb_funcall2(rb_cTime, oj_new_id, 7, args); } else { parent->val = rb_time_nano_new(ni->i, (long)nsec); } rval = parent->val; } else { rb_hash_aset(parent->val, calc_hash_key(pi, kval), rval); } break; default: break; } if (Yes == pi->options.trace) { oj_trace_parse_call("set_string", pi, __FILE__, __LINE__, rval); } } static void hash_set_value(ParseInfo pi, Val kval, VALUE value) { Val parent = stack_peek(&pi->stack); switch (rb_type(parent->val)) { case T_OBJECT: oj_set_obj_ivar(parent, kval, value); break; case T_HASH: rb_hash_aset(parent->val, calc_hash_key(pi, kval), value); break; default: break; } if (Yes == pi->options.trace) { oj_trace_parse_call("set_value", pi, __FILE__, __LINE__, value); } } static void array_append_num(ParseInfo pi, NumInfo ni) { Val parent = stack_peek(&pi->stack); volatile VALUE rval = oj_num_as_value(ni); rb_ary_push(parent->val, rval); if (Yes == pi->options.trace) { oj_trace_parse_call("append_number", pi, __FILE__, __LINE__, rval); } } static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { volatile VALUE rstr = rb_str_new(str, len); rstr = oj_encode(rstr); if (Yes == pi->options.create_ok && NULL != pi->options.str_rx.head) { VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, (int)len); if (Qnil != clas) { rb_ary_push(stack_peek(&pi->stack)->val, rb_funcall(clas, oj_json_create_id, 1, rstr)); return; } } rb_ary_push(stack_peek(&pi->stack)->val, rstr); if (Yes == pi->options.trace) { oj_trace_parse_call("append_string", pi, __FILE__, __LINE__, rstr); } } void oj_set_custom_callbacks(ParseInfo pi) { oj_set_compat_callbacks(pi); pi->hash_set_cstr = hash_set_cstr; pi->end_hash = end_hash; pi->hash_set_num = hash_set_num; pi->hash_set_value = hash_set_value; pi->array_append_cstr = array_append_cstr; pi->array_append_num = array_append_num; } VALUE oj_custom_parse(int argc, VALUE *argv, VALUE self) { struct _parseInfo pi; parse_info_init(&pi); pi.options = oj_default_options; pi.handler = Qnil; pi.err_class = Qnil; pi.max_depth = 0; pi.options.allow_nan = Yes; pi.options.nilnil = Yes; oj_set_custom_callbacks(&pi); if (T_STRING == rb_type(*argv)) { return oj_pi_parse(argc, argv, &pi, 0, 0, false); } else { return oj_pi_sparse(argc, argv, &pi, 0); } } VALUE oj_custom_parse_cstr(int argc, VALUE *argv, char *json, size_t len) { struct _parseInfo pi; parse_info_init(&pi); pi.options = oj_default_options; pi.handler = Qnil; pi.err_class = Qnil; pi.max_depth = 0; pi.options.allow_nan = Yes; pi.options.nilnil = Yes; oj_set_custom_callbacks(&pi); pi.end_hash = end_hash; return oj_pi_parse(argc, argv, &pi, json, len, false); }