Commit 194ad621 authored by Josh Haberman's avatar Josh Haberman

Ruby JSON: always accept both camelCase and original field names.

For JSON encoding we provide a new option to decide at
encode time whether to use camelCase or original proto field
names:

  json = MapMessage.encode_json(m, :preserve_proto_fieldnames => true)
parent 90c7f6e5
...@@ -255,6 +255,10 @@ void Descriptor_free(void* _self) { ...@@ -255,6 +255,10 @@ void Descriptor_free(void* _self) {
upb_handlers_unref(self->json_serialize_handlers, upb_handlers_unref(self->json_serialize_handlers,
&self->json_serialize_handlers); &self->json_serialize_handlers);
} }
if (self->json_serialize_handlers_preserve) {
upb_handlers_unref(self->json_serialize_handlers_preserve,
&self->json_serialize_handlers_preserve);
}
xfree(self); xfree(self);
} }
...@@ -278,6 +282,7 @@ VALUE Descriptor_alloc(VALUE klass) { ...@@ -278,6 +282,7 @@ VALUE Descriptor_alloc(VALUE klass) {
self->json_fill_method = NULL; self->json_fill_method = NULL;
self->pb_serialize_handlers = NULL; self->pb_serialize_handlers = NULL;
self->json_serialize_handlers = NULL; self->json_serialize_handlers = NULL;
self->json_serialize_handlers_preserve = NULL;
self->typeclass_references = rb_ary_new(); self->typeclass_references = rb_ary_new();
return ret; return ret;
} }
......
...@@ -1130,13 +1130,23 @@ static const upb_handlers* msgdef_pb_serialize_handlers(Descriptor* desc) { ...@@ -1130,13 +1130,23 @@ static const upb_handlers* msgdef_pb_serialize_handlers(Descriptor* desc) {
return desc->pb_serialize_handlers; return desc->pb_serialize_handlers;
} }
static const upb_handlers* msgdef_json_serialize_handlers(Descriptor* desc) { static const upb_handlers* msgdef_json_serialize_handlers(
Descriptor* desc, bool preserve_proto_fieldnames) {
if (preserve_proto_fieldnames) {
if (desc->json_serialize_handlers == NULL) { if (desc->json_serialize_handlers == NULL) {
desc->json_serialize_handlers = desc->json_serialize_handlers =
upb_json_printer_newhandlers( upb_json_printer_newhandlers(
desc->msgdef, &desc->json_serialize_handlers); desc->msgdef, true, &desc->json_serialize_handlers);
} }
return desc->json_serialize_handlers; return desc->json_serialize_handlers;
} else {
if (desc->json_serialize_handlers_preserve == NULL) {
desc->json_serialize_handlers_preserve =
upb_json_printer_newhandlers(
desc->msgdef, false, &desc->json_serialize_handlers_preserve);
}
return desc->json_serialize_handlers_preserve;
}
} }
/* /*
...@@ -1181,16 +1191,33 @@ VALUE Message_encode(VALUE klass, VALUE msg_rb) { ...@@ -1181,16 +1191,33 @@ VALUE Message_encode(VALUE klass, VALUE msg_rb) {
* *
* Encodes the given message object into its serialized JSON representation. * Encodes the given message object into its serialized JSON representation.
*/ */
VALUE Message_encode_json(VALUE klass, VALUE msg_rb) { VALUE Message_encode_json(int argc, VALUE* argv, VALUE klass) {
VALUE descriptor = rb_ivar_get(klass, descriptor_instancevar_interned); VALUE descriptor = rb_ivar_get(klass, descriptor_instancevar_interned);
Descriptor* desc = ruby_to_Descriptor(descriptor); Descriptor* desc = ruby_to_Descriptor(descriptor);
VALUE msg_rb;
VALUE preserve_proto_fieldnames = Qfalse;
stringsink sink; stringsink sink;
if (argc < 1 || argc > 2) {
rb_raise(rb_eArgError, "Expected 1 or 2 arguments.");
}
msg_rb = argv[0];
if (argc == 2) {
VALUE hash_args = argv[1];
if (TYPE(hash_args) != T_HASH) {
rb_raise(rb_eArgError, "Expected hash arguments.");
}
preserve_proto_fieldnames = rb_hash_lookup2(
hash_args, ID2SYM(rb_intern("preserve_proto_fieldnames")), Qfalse);
}
stringsink_init(&sink); stringsink_init(&sink);
{ {
const upb_handlers* serialize_handlers = const upb_handlers* serialize_handlers =
msgdef_json_serialize_handlers(desc); msgdef_json_serialize_handlers(desc, RTEST(preserve_proto_fieldnames));
upb_json_printer* printer; upb_json_printer* printer;
stackenv se; stackenv se;
VALUE ret; VALUE ret;
......
...@@ -475,7 +475,7 @@ VALUE build_class_from_descriptor(Descriptor* desc) { ...@@ -475,7 +475,7 @@ VALUE build_class_from_descriptor(Descriptor* desc) {
rb_define_singleton_method(klass, "decode", Message_decode, 1); rb_define_singleton_method(klass, "decode", Message_decode, 1);
rb_define_singleton_method(klass, "encode", Message_encode, 1); rb_define_singleton_method(klass, "encode", Message_encode, 1);
rb_define_singleton_method(klass, "decode_json", Message_decode_json, 1); rb_define_singleton_method(klass, "decode_json", Message_decode_json, 1);
rb_define_singleton_method(klass, "encode_json", Message_encode_json, 1); rb_define_singleton_method(klass, "encode_json", Message_encode_json, -1);
rb_define_singleton_method(klass, "descriptor", Message_descriptor, 0); rb_define_singleton_method(klass, "descriptor", Message_descriptor, 0);
return klass; return klass;
......
...@@ -115,6 +115,7 @@ struct Descriptor { ...@@ -115,6 +115,7 @@ struct Descriptor {
const upb_json_parsermethod* json_fill_method; const upb_json_parsermethod* json_fill_method;
const upb_handlers* pb_serialize_handlers; const upb_handlers* pb_serialize_handlers;
const upb_handlers* json_serialize_handlers; const upb_handlers* json_serialize_handlers;
const upb_handlers* json_serialize_handlers_preserve;
// Handlers hold type class references for sub-message fields directly in some // Handlers hold type class references for sub-message fields directly in some
// cases. We need to keep these rooted because they might otherwise be // cases. We need to keep these rooted because they might otherwise be
// collected. // collected.
...@@ -498,7 +499,7 @@ VALUE Message_descriptor(VALUE klass); ...@@ -498,7 +499,7 @@ VALUE Message_descriptor(VALUE klass);
VALUE Message_decode(VALUE klass, VALUE data); VALUE Message_decode(VALUE klass, VALUE data);
VALUE Message_encode(VALUE klass, VALUE msg_rb); VALUE Message_encode(VALUE klass, VALUE msg_rb);
VALUE Message_decode_json(VALUE klass, VALUE data); VALUE Message_decode_json(VALUE klass, VALUE data);
VALUE Message_encode_json(VALUE klass, VALUE msg_rb); VALUE Message_encode_json(int argc, VALUE* argv, VALUE klass);
VALUE Google_Protobuf_deep_copy(VALUE self, VALUE obj); VALUE Google_Protobuf_deep_copy(VALUE self, VALUE obj);
......
...@@ -11624,7 +11624,7 @@ _again: ...@@ -11624,7 +11624,7 @@ _again:
#line 1270 "upb/json/parser.rl" #line 1270 "upb/json/parser.rl"
if (p != pe) { if (p != pe) {
upb_status_seterrf(&parser->status, "Parse error at %s\n", p); upb_status_seterrf(&parser->status, "Parse error at '%.*s'\n", p, pe - p);
upb_env_reporterror(parser->env, &parser->status); upb_env_reporterror(parser->env, &parser->status);
} else { } else {
capture_suspend(parser, &p); capture_suspend(parser, &p);
...@@ -11725,6 +11725,7 @@ static void add_jsonname_table(upb_json_parsermethod *m, const upb_msgdef* md) { ...@@ -11725,6 +11725,7 @@ static void add_jsonname_table(upb_json_parsermethod *m, const upb_msgdef* md) {
upb_msg_field_next(&i)) { upb_msg_field_next(&i)) {
const upb_fielddef *f = upb_msg_iter_field(&i); const upb_fielddef *f = upb_msg_iter_field(&i);
/* Add an entry for the JSON name. */
size_t field_len = upb_fielddef_getjsonname(f, buf, len); size_t field_len = upb_fielddef_getjsonname(f, buf, len);
if (field_len > len) { if (field_len > len) {
size_t len2; size_t len2;
...@@ -11735,10 +11736,10 @@ static void add_jsonname_table(upb_json_parsermethod *m, const upb_msgdef* md) { ...@@ -11735,10 +11736,10 @@ static void add_jsonname_table(upb_json_parsermethod *m, const upb_msgdef* md) {
} }
upb_strtable_insert(t, buf, upb_value_constptr(f)); upb_strtable_insert(t, buf, upb_value_constptr(f));
if (getenv("UPB_JSON_ACCEPT_LEGACY_FIELD_NAMES")) { if (strcmp(buf, upb_fielddef_name(f)) != 0) {
/* Temporary code to help people migrate if they were depending on the /* Since the JSON name is different from the regular field name, add an
* old, non-proto3-json-compliant field names. In this case we * entry for the raw name (compliant proto3 JSON parsers must accept
* recognize both old names and new names. */ * both). */
upb_strtable_insert(t, upb_fielddef_name(f), upb_value_constptr(f)); upb_strtable_insert(t, upb_fielddef_name(f), upb_value_constptr(f));
} }
...@@ -11853,12 +11854,11 @@ void freestrpc(void *ptr) { ...@@ -11853,12 +11854,11 @@ void freestrpc(void *ptr) {
} }
/* Convert fielddef name to JSON name and return as a string piece. */ /* Convert fielddef name to JSON name and return as a string piece. */
strpc *newstrpc(upb_handlers *h, const upb_fielddef *f) { strpc *newstrpc(upb_handlers *h, const upb_fielddef *f,
bool preserve_fieldnames) {
/* TODO(haberman): handle malloc failure. */ /* TODO(haberman): handle malloc failure. */
strpc *ret = malloc(sizeof(*ret)); strpc *ret = malloc(sizeof(*ret));
if (getenv("UPB_JSON_WRITE_LEGACY_FIELD_NAMES")) { if (preserve_fieldnames) {
/* Temporary code to help people migrate if they were depending on the
* old, non-proto3-json-compliant field names. */
ret->ptr = upb_strdup(upb_fielddef_name(f)); ret->ptr = upb_strdup(upb_fielddef_name(f));
ret->len = strlen(ret->ptr); ret->len = strlen(ret->ptr);
} else { } else {
...@@ -12374,10 +12374,11 @@ static size_t mapkey_bytes(void *closure, const void *handler_data, ...@@ -12374,10 +12374,11 @@ static size_t mapkey_bytes(void *closure, const void *handler_data,
static void set_enum_hd(upb_handlers *h, static void set_enum_hd(upb_handlers *h,
const upb_fielddef *f, const upb_fielddef *f,
bool preserve_fieldnames,
upb_handlerattr *attr) { upb_handlerattr *attr) {
EnumHandlerData *hd = malloc(sizeof(EnumHandlerData)); EnumHandlerData *hd = malloc(sizeof(EnumHandlerData));
hd->enumdef = (const upb_enumdef *)upb_fielddef_subdef(f); hd->enumdef = (const upb_enumdef *)upb_fielddef_subdef(f);
hd->keyname = newstrpc(h, f); hd->keyname = newstrpc(h, f, preserve_fieldnames);
upb_handlers_addcleanup(h, hd, free); upb_handlers_addcleanup(h, hd, free);
upb_handlerattr_sethandlerdata(attr, hd); upb_handlerattr_sethandlerdata(attr, hd);
} }
...@@ -12394,7 +12395,8 @@ static void set_enum_hd(upb_handlers *h, ...@@ -12394,7 +12395,8 @@ static void set_enum_hd(upb_handlers *h,
* our sources that emit mapentry messages do so canonically (with one key * our sources that emit mapentry messages do so canonically (with one key
* field, and then one value field), so this is not a pressing concern at the * field, and then one value field), so this is not a pressing concern at the
* moment. */ * moment. */
void printer_sethandlers_mapentry(const void *closure, upb_handlers *h) { void printer_sethandlers_mapentry(const void *closure, bool preserve_fieldnames,
upb_handlers *h) {
const upb_msgdef *md = upb_handlers_msgdef(h); const upb_msgdef *md = upb_handlers_msgdef(h);
/* A mapentry message is printed simply as '"key": value'. Rather than /* A mapentry message is printed simply as '"key": value'. Rather than
...@@ -12468,7 +12470,7 @@ void printer_sethandlers_mapentry(const void *closure, upb_handlers *h) { ...@@ -12468,7 +12470,7 @@ void printer_sethandlers_mapentry(const void *closure, upb_handlers *h) {
break; break;
case UPB_TYPE_ENUM: { case UPB_TYPE_ENUM: {
upb_handlerattr enum_attr = UPB_HANDLERATTR_INITIALIZER; upb_handlerattr enum_attr = UPB_HANDLERATTR_INITIALIZER;
set_enum_hd(h, value_field, &enum_attr); set_enum_hd(h, value_field, preserve_fieldnames, &enum_attr);
upb_handlers_setint32(h, value_field, mapvalue_enum, &enum_attr); upb_handlers_setint32(h, value_field, mapvalue_enum, &enum_attr);
upb_handlerattr_uninit(&enum_attr); upb_handlerattr_uninit(&enum_attr);
break; break;
...@@ -12487,13 +12489,13 @@ void printer_sethandlers(const void *closure, upb_handlers *h) { ...@@ -12487,13 +12489,13 @@ void printer_sethandlers(const void *closure, upb_handlers *h) {
bool is_mapentry = upb_msgdef_mapentry(md); bool is_mapentry = upb_msgdef_mapentry(md);
upb_handlerattr empty_attr = UPB_HANDLERATTR_INITIALIZER; upb_handlerattr empty_attr = UPB_HANDLERATTR_INITIALIZER;
upb_msg_field_iter i; upb_msg_field_iter i;
const bool *preserve_fieldnames_ptr = closure;
UPB_UNUSED(closure); const bool preserve_fieldnames = *preserve_fieldnames_ptr;
if (is_mapentry) { if (is_mapentry) {
/* mapentry messages are sufficiently different that we handle them /* mapentry messages are sufficiently different that we handle them
* separately. */ * separately. */
printer_sethandlers_mapentry(closure, h); printer_sethandlers_mapentry(closure, preserve_fieldnames, h);
return; return;
} }
...@@ -12514,7 +12516,8 @@ void printer_sethandlers(const void *closure, upb_handlers *h) { ...@@ -12514,7 +12516,8 @@ void printer_sethandlers(const void *closure, upb_handlers *h) {
const upb_fielddef *f = upb_msg_iter_field(&i); const upb_fielddef *f = upb_msg_iter_field(&i);
upb_handlerattr name_attr = UPB_HANDLERATTR_INITIALIZER; upb_handlerattr name_attr = UPB_HANDLERATTR_INITIALIZER;
upb_handlerattr_sethandlerdata(&name_attr, newstrpc(h, f)); upb_handlerattr_sethandlerdata(&name_attr,
newstrpc(h, f, preserve_fieldnames));
if (upb_fielddef_ismap(f)) { if (upb_fielddef_ismap(f)) {
upb_handlers_setstartseq(h, f, startmap, &name_attr); upb_handlers_setstartseq(h, f, startmap, &name_attr);
...@@ -12537,7 +12540,7 @@ void printer_sethandlers(const void *closure, upb_handlers *h) { ...@@ -12537,7 +12540,7 @@ void printer_sethandlers(const void *closure, upb_handlers *h) {
* option later to control this behavior, but we will wait for a real * option later to control this behavior, but we will wait for a real
* need first. */ * need first. */
upb_handlerattr enum_attr = UPB_HANDLERATTR_INITIALIZER; upb_handlerattr enum_attr = UPB_HANDLERATTR_INITIALIZER;
set_enum_hd(h, f, &enum_attr); set_enum_hd(h, f, preserve_fieldnames, &enum_attr);
if (upb_fielddef_isseq(f)) { if (upb_fielddef_isseq(f)) {
upb_handlers_setint32(h, f, repeated_enum, &enum_attr); upb_handlers_setint32(h, f, repeated_enum, &enum_attr);
...@@ -12614,6 +12617,8 @@ upb_sink *upb_json_printer_input(upb_json_printer *p) { ...@@ -12614,6 +12617,8 @@ upb_sink *upb_json_printer_input(upb_json_printer *p) {
} }
const upb_handlers *upb_json_printer_newhandlers(const upb_msgdef *md, const upb_handlers *upb_json_printer_newhandlers(const upb_msgdef *md,
bool preserve_fieldnames,
const void *owner) { const void *owner) {
return upb_handlers_newfrozen(md, owner, printer_sethandlers, NULL); return upb_handlers_newfrozen(
md, owner, printer_sethandlers, &preserve_fieldnames);
} }
...@@ -8370,8 +8370,12 @@ class upb::json::Printer { ...@@ -8370,8 +8370,12 @@ class upb::json::Printer {
/* The input to the printer. */ /* The input to the printer. */
Sink* input(); Sink* input();
/* Returns handlers for printing according to the specified schema. */ /* Returns handlers for printing according to the specified schema.
static reffed_ptr<const Handlers> NewHandlers(const upb::MessageDef* md); * If preserve_proto_fieldnames is true, the output JSON will use the
* original .proto field names (ie. {"my_field":3}) instead of using
* camelCased names, which is the default: (eg. {"myField":3}). */
static reffed_ptr<const Handlers> NewHandlers(const upb::MessageDef* md,
bool preserve_proto_fieldnames);
static const size_t kSize = UPB_JSON_PRINTER_SIZE; static const size_t kSize = UPB_JSON_PRINTER_SIZE;
...@@ -8388,6 +8392,7 @@ upb_json_printer *upb_json_printer_create(upb_env *e, const upb_handlers *h, ...@@ -8388,6 +8392,7 @@ upb_json_printer *upb_json_printer_create(upb_env *e, const upb_handlers *h,
upb_bytessink *output); upb_bytessink *output);
upb_sink *upb_json_printer_input(upb_json_printer *p); upb_sink *upb_json_printer_input(upb_json_printer *p);
const upb_handlers *upb_json_printer_newhandlers(const upb_msgdef *md, const upb_handlers *upb_json_printer_newhandlers(const upb_msgdef *md,
bool preserve_fieldnames,
const void *owner); const void *owner);
UPB_END_EXTERN_C UPB_END_EXTERN_C
...@@ -8402,8 +8407,9 @@ inline Printer* Printer::Create(Environment* env, const upb::Handlers* handlers, ...@@ -8402,8 +8407,9 @@ inline Printer* Printer::Create(Environment* env, const upb::Handlers* handlers,
} }
inline Sink* Printer::input() { return upb_json_printer_input(this); } inline Sink* Printer::input() { return upb_json_printer_input(this); }
inline reffed_ptr<const Handlers> Printer::NewHandlers( inline reffed_ptr<const Handlers> Printer::NewHandlers(
const upb::MessageDef *md) { const upb::MessageDef *md, bool preserve_proto_fieldnames) {
const Handlers* h = upb_json_printer_newhandlers(md, &h); const Handlers* h = upb_json_printer_newhandlers(
md, preserve_proto_fieldnames, &h);
return reffed_ptr<const Handlers>(h, &h); return reffed_ptr<const Handlers>(h, &h);
} }
} /* namespace json */ } /* namespace json */
......
...@@ -1161,7 +1161,12 @@ module BasicTest ...@@ -1161,7 +1161,12 @@ module BasicTest
return if RUBY_PLATFORM == "java" return if RUBY_PLATFORM == "java"
m = MapMessage.new(:map_string_int32 => {"a" => 1}) m = MapMessage.new(:map_string_int32 => {"a" => 1})
expected = '{"mapStringInt32":{"a":1},"mapStringMsg":{}}' expected = '{"mapStringInt32":{"a":1},"mapStringMsg":{}}'
expected_preserve = '{"map_string_int32":{"a":1},"map_string_msg":{}}'
assert MapMessage.encode_json(m) == expected assert MapMessage.encode_json(m) == expected
json = MapMessage.encode_json(m, :preserve_proto_fieldnames => true)
assert json == expected_preserve
m2 = MapMessage.decode_json(MapMessage.encode_json(m)) m2 = MapMessage.decode_json(MapMessage.encode_json(m))
assert m == m2 assert m == m2
end end
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment