All of lore.kernel.org
 help / color / mirror / Atom feed
* json parsing/decoding
@ 2013-02-06 22:19 Yehuda Sadeh
  2013-02-06 22:23 ` Sage Weil
  0 siblings, 1 reply; 5+ messages in thread
From: Yehuda Sadeh @ 2013-02-06 22:19 UTC (permalink / raw)
  To: ceph-devel

The matter of json decoding was brought up as part of the discussion
of the ceph management api. I recently had to deal with it with the
rados gateway, as I wasn't too happy was the current mechanisms that
were used for this purpose. So I introduced a new decode_json()
functionality (now in common/ceph_json.* at the wip-json-decode
branch). It's very similar to the regular encode/decode stuff that we
use to encode/decode structures. Encoding is being done using the
already existing dump() method, and decoding will be done through the
new decode_json() method.

As an example we'll define a new UserInfo structure:

struct UserInfo {
  string uid;
  string display_name;
  int max_buckets;
  list<Key> keys;

  void dump(Formatter *f) const;
  void decode_json(JSONObj *obj);
};

The dump() method will look like this:

void UserInfo::dump(Formatter *f) const
{
  f->open_object_section("user_info");
  f->dump_string("user_id", uid);
  f->dump_string("display_name", display_name);
  f->dump_int("max_buckets", (int)max_buckets);

  f->open_array_section("keys");
  for (list<Key>::iterator iter = keys.begin(); siter != keys.end(); ++iter) {
    iter->dump(f);
  }
  f->close_section();

  f->close_section();
}

It is recommended that every dump() method opens an object section and
puts everything within, so that every object could be easily embedded
within other objects. We can also probably create some template that
will remove the need of doing that inner loop, e.g.:
  ...
  f->dump_array("keys", keys);
  ...

In any case, the corresponding json decoding function looks like this:

void UserInfo::decode_json(JSONObj *obj) {
  JSONDecoder::decode_json("user_id", uid, obj);
  JSONDecoder::decode_json("display_name", display_name, obj);
  JSONDecoder::decode_json("max_buckets", max_buckets, obj);
  JSONDecoder::decode_json("keys", keys, obj);
}


and we'll decode the structure like this:

...

UserInfo user_info;

JSONParser parser;

if (!parser.parse(buf, len))
  return -EINVAL;
...
try {
  user_info.decode(&parser);
} catch (JSONDecoder::err& e) {
...
}


The default behavior is that every field is optional, so that it just
ignores fields that are not found in the json structure. If a field is
set to be mandatory (by extra param to the decode_json()), then an
exception will be thrown. Note that all fields should be initialized
to the default value before decoding as passed json might not contain
any relevant field.


Yehuda

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: json parsing/decoding
  2013-02-06 22:19 json parsing/decoding Yehuda Sadeh
@ 2013-02-06 22:23 ` Sage Weil
  2013-02-06 22:36   ` Yehuda Sadeh
  0 siblings, 1 reply; 5+ messages in thread
From: Sage Weil @ 2013-02-06 22:23 UTC (permalink / raw)
  To: Yehuda Sadeh; +Cc: ceph-devel

On Wed, 6 Feb 2013, Yehuda Sadeh wrote:
> The matter of json decoding was brought up as part of the discussion
> of the ceph management api. I recently had to deal with it with the
> rados gateway, as I wasn't too happy was the current mechanisms that
> were used for this purpose. So I introduced a new decode_json()
> functionality (now in common/ceph_json.* at the wip-json-decode
> branch). It's very similar to the regular encode/decode stuff that we
> use to encode/decode structures. Encoding is being done using the
> already existing dump() method, and decoding will be done through the
> new decode_json() method.
> 
> As an example we'll define a new UserInfo structure:
> 
> struct UserInfo {
>   string uid;
>   string display_name;
>   int max_buckets;
>   list<Key> keys;
> 
>   void dump(Formatter *f) const;
>   void decode_json(JSONObj *obj);
> };
> 
> The dump() method will look like this:
> 
> void UserInfo::dump(Formatter *f) const
> {
>   f->open_object_section("user_info");
>   f->dump_string("user_id", uid);
>   f->dump_string("display_name", display_name);
>   f->dump_int("max_buckets", (int)max_buckets);
> 
>   f->open_array_section("keys");
>   for (list<Key>::iterator iter = keys.begin(); siter != keys.end(); ++iter) {
>     iter->dump(f);
>   }
>   f->close_section();
> 
>   f->close_section();
> }
> 
> It is recommended that every dump() method opens an object section and
> puts everything within, so that every object could be easily embedded
> within other objects. We can also probably create some template that

The current loose convention is that the dump objects do not define the 
container they dump into.  It's a bit weird, but it lets you do things 
like:

map<Key, Value> foo;

   f->open_array_section("keys");
   for (map<Key,Value>::iterator iter = keys.begin(); siter != keys.end(); ++iter) {
     f->open_object_section("thing");
     f->dump_string("key", iter->first);
     iter->second.dump(f);
     f->close_section();
   }
   f->close_section();

Admittedly that doesn't come up *that* often, but it happened enough when 
we first started doing the dump stuff that all/most of the dumpers work 
that way.

How should we deal with that?

sage


> will remove the need of doing that inner loop, e.g.:
>   ...
>   f->dump_array("keys", keys);
>   ...
> 
> In any case, the corresponding json decoding function looks like this:
> 
> void UserInfo::decode_json(JSONObj *obj) {
>   JSONDecoder::decode_json("user_id", uid, obj);
>   JSONDecoder::decode_json("display_name", display_name, obj);
>   JSONDecoder::decode_json("max_buckets", max_buckets, obj);
>   JSONDecoder::decode_json("keys", keys, obj);
> }
> 
> 
> and we'll decode the structure like this:
> 
> ...
> 
> UserInfo user_info;
> 
> JSONParser parser;
> 
> if (!parser.parse(buf, len))
>   return -EINVAL;
> ...
> try {
>   user_info.decode(&parser);
> } catch (JSONDecoder::err& e) {
> ...
> }
> 
> 
> The default behavior is that every field is optional, so that it just
> ignores fields that are not found in the json structure. If a field is
> set to be mandatory (by extra param to the decode_json()), then an
> exception will be thrown. Note that all fields should be initialized
> to the default value before decoding as passed json might not contain
> any relevant field.
> 
> 
> Yehuda
> --
> To unsubscribe from this list: send the line "unsubscribe ceph-devel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
> 

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: json parsing/decoding
  2013-02-06 22:23 ` Sage Weil
@ 2013-02-06 22:36   ` Yehuda Sadeh
  2013-02-07  0:53     ` Sage Weil
  0 siblings, 1 reply; 5+ messages in thread
From: Yehuda Sadeh @ 2013-02-06 22:36 UTC (permalink / raw)
  To: Sage Weil; +Cc: ceph-devel

On Wed, Feb 6, 2013 at 2:23 PM, Sage Weil <sage@inktank.com> wrote:
> On Wed, 6 Feb 2013, Yehuda Sadeh wrote:
>> The matter of json decoding was brought up as part of the discussion
>> of the ceph management api. I recently had to deal with it with the
>> rados gateway, as I wasn't too happy was the current mechanisms that
>> were used for this purpose. So I introduced a new decode_json()
>> functionality (now in common/ceph_json.* at the wip-json-decode
>> branch). It's very similar to the regular encode/decode stuff that we
>> use to encode/decode structures. Encoding is being done using the
>> already existing dump() method, and decoding will be done through the
>> new decode_json() method.
>>
>> As an example we'll define a new UserInfo structure:
>>
>> struct UserInfo {
>>   string uid;
>>   string display_name;
>>   int max_buckets;
>>   list<Key> keys;
>>
>>   void dump(Formatter *f) const;
>>   void decode_json(JSONObj *obj);
>> };
>>
>> The dump() method will look like this:
>>
>> void UserInfo::dump(Formatter *f) const
>> {
>>   f->open_object_section("user_info");
>>   f->dump_string("user_id", uid);
>>   f->dump_string("display_name", display_name);
>>   f->dump_int("max_buckets", (int)max_buckets);
>>
>>   f->open_array_section("keys");
>>   for (list<Key>::iterator iter = keys.begin(); siter != keys.end(); ++iter) {
>>     iter->dump(f);
>>   }
>>   f->close_section();
>>
>>   f->close_section();
>> }
>>
>> It is recommended that every dump() method opens an object section and
>> puts everything within, so that every object could be easily embedded
>> within other objects. We can also probably create some template that
>
> The current loose convention is that the dump objects do not define the
> container they dump into.  It's a bit weird, but it lets you do things
> like:
>
> map<Key, Value> foo;
>
>    f->open_array_section("keys");
>    for (map<Key,Value>::iterator iter = keys.begin(); siter != keys.end(); ++iter) {
>      f->open_object_section("thing");
>      f->dump_string("key", iter->first);
>      iter->second.dump(f);
>      f->close_section();
>    }
>    f->close_section();
>
> Admittedly that doesn't come up *that* often, but it happened enough when
> we first started doing the dump stuff that all/most of the dumpers work
> that way.
>
> How should we deal with that?
>

Modify the dumpers to not work this way? It's a matter of conventions,
not set in stone. If we keep this form then we'd need to create
decoders for these records:

struct KeyRecord {
  Key k;
  Value v;

  void decode_json(JSONObj *obj) {
    JSONDecoder::decode_json("key", key, obj);
    v.decode_json(obj);
  }
};

This will work, assuming Value doesn't have any field indexed by "key".

Yehuda

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: json parsing/decoding
  2013-02-06 22:36   ` Yehuda Sadeh
@ 2013-02-07  0:53     ` Sage Weil
  2013-02-07  1:05       ` Yehuda Sadeh
  0 siblings, 1 reply; 5+ messages in thread
From: Sage Weil @ 2013-02-07  0:53 UTC (permalink / raw)
  To: Yehuda Sadeh; +Cc: ceph-devel

On Wed, 6 Feb 2013, Yehuda Sadeh wrote:
> On Wed, Feb 6, 2013 at 2:23 PM, Sage Weil <sage@inktank.com> wrote:
> > On Wed, 6 Feb 2013, Yehuda Sadeh wrote:
> >> The matter of json decoding was brought up as part of the discussion
> >> of the ceph management api. I recently had to deal with it with the
> >> rados gateway, as I wasn't too happy was the current mechanisms that
> >> were used for this purpose. So I introduced a new decode_json()
> >> functionality (now in common/ceph_json.* at the wip-json-decode
> >> branch). It's very similar to the regular encode/decode stuff that we
> >> use to encode/decode structures. Encoding is being done using the
> >> already existing dump() method, and decoding will be done through the
> >> new decode_json() method.
> >>
> >> As an example we'll define a new UserInfo structure:
> >>
> >> struct UserInfo {
> >>   string uid;
> >>   string display_name;
> >>   int max_buckets;
> >>   list<Key> keys;
> >>
> >>   void dump(Formatter *f) const;
> >>   void decode_json(JSONObj *obj);
> >> };
> >>
> >> The dump() method will look like this:
> >>
> >> void UserInfo::dump(Formatter *f) const
> >> {
> >>   f->open_object_section("user_info");
> >>   f->dump_string("user_id", uid);
> >>   f->dump_string("display_name", display_name);
> >>   f->dump_int("max_buckets", (int)max_buckets);
> >>
> >>   f->open_array_section("keys");
> >>   for (list<Key>::iterator iter = keys.begin(); siter != keys.end(); ++iter) {
> >>     iter->dump(f);
> >>   }
> >>   f->close_section();
> >>
> >>   f->close_section();
> >> }
> >>
> >> It is recommended that every dump() method opens an object section and
> >> puts everything within, so that every object could be easily embedded
> >> within other objects. We can also probably create some template that
> >
> > The current loose convention is that the dump objects do not define the
> > container they dump into.  It's a bit weird, but it lets you do things
> > like:
> >
> > map<Key, Value> foo;
> >
> >    f->open_array_section("keys");
> >    for (map<Key,Value>::iterator iter = keys.begin(); siter != keys.end(); ++iter) {
> >      f->open_object_section("thing");
> >      f->dump_string("key", iter->first);
> >      iter->second.dump(f);
> >      f->close_section();
> >    }
> >    f->close_section();
> >
> > Admittedly that doesn't come up *that* often, but it happened enough when
> > we first started doing the dump stuff that all/most of the dumpers work
> > that way.
> >
> > How should we deal with that?
> >
> 
> Modify the dumpers to not work this way? It's a matter of conventions,
> not set in stone. If we keep this form then we'd need to create
> decoders for these records:
> 
> struct KeyRecord {
>   Key k;
>   Value v;
> 
>   void decode_json(JSONObj *obj) {
>     JSONDecoder::decode_json("key", key, obj);
>     v.decode_json(obj);
>   }
> };
> 
> This will work, assuming Value doesn't have any field indexed by "key".

I think it mostly comes up in dumping other structures, not parsing, so it 
may be mostly ok on that end. I'm not sure how to manage dumping, though, 
if the section is opened inside the value object's dump().

This also worries me: ceph_dencoder does

      jf.open_object_section("object");
      den->dump(&jf);
      jf.close_section();

which means that this sort of change will probably the type 
encoding/decode unit tests we run.

Maybe something like

void encode_json(Formatter *f) const
{
  f->open_object_section("mytype");
  dump(f);
  f->close_section();
}

will give us the cleaner interface you're looking for.  And the 
ceph-dencoder type definitions can mark types which implement one or the 
other... or we can implement the encode_json one for all objects as a 
wrapper via a macro or something we don't have to duplicate teh 
boilerplate everywhere?

sage

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: json parsing/decoding
  2013-02-07  0:53     ` Sage Weil
@ 2013-02-07  1:05       ` Yehuda Sadeh
  0 siblings, 0 replies; 5+ messages in thread
From: Yehuda Sadeh @ 2013-02-07  1:05 UTC (permalink / raw)
  To: Sage Weil; +Cc: ceph-devel

On Wed, Feb 6, 2013 at 4:53 PM, Sage Weil <sage@inktank.com> wrote:
> On Wed, 6 Feb 2013, Yehuda Sadeh wrote:
>> On Wed, Feb 6, 2013 at 2:23 PM, Sage Weil <sage@inktank.com> wrote:
>> > On Wed, 6 Feb 2013, Yehuda Sadeh wrote:
>> >> The matter of json decoding was brought up as part of the discussion
>> >> of the ceph management api. I recently had to deal with it with the
>> >> rados gateway, as I wasn't too happy was the current mechanisms that
>> >> were used for this purpose. So I introduced a new decode_json()
>> >> functionality (now in common/ceph_json.* at the wip-json-decode
>> >> branch). It's very similar to the regular encode/decode stuff that we
>> >> use to encode/decode structures. Encoding is being done using the
>> >> already existing dump() method, and decoding will be done through the
>> >> new decode_json() method.
>> >>
>> >> As an example we'll define a new UserInfo structure:
>> >>
>> >> struct UserInfo {
>> >>   string uid;
>> >>   string display_name;
>> >>   int max_buckets;
>> >>   list<Key> keys;
>> >>
>> >>   void dump(Formatter *f) const;
>> >>   void decode_json(JSONObj *obj);
>> >> };
>> >>
>> >> The dump() method will look like this:
>> >>
>> >> void UserInfo::dump(Formatter *f) const
>> >> {
>> >>   f->open_object_section("user_info");
>> >>   f->dump_string("user_id", uid);
>> >>   f->dump_string("display_name", display_name);
>> >>   f->dump_int("max_buckets", (int)max_buckets);
>> >>
>> >>   f->open_array_section("keys");
>> >>   for (list<Key>::iterator iter = keys.begin(); siter != keys.end(); ++iter) {
>> >>     iter->dump(f);
>> >>   }
>> >>   f->close_section();
>> >>
>> >>   f->close_section();
>> >> }
>> >>
>> >> It is recommended that every dump() method opens an object section and
>> >> puts everything within, so that every object could be easily embedded
>> >> within other objects. We can also probably create some template that
>> >
>> > The current loose convention is that the dump objects do not define the
>> > container they dump into.  It's a bit weird, but it lets you do things
>> > like:
>> >
>> > map<Key, Value> foo;
>> >
>> >    f->open_array_section("keys");
>> >    for (map<Key,Value>::iterator iter = keys.begin(); siter != keys.end(); ++iter) {
>> >      f->open_object_section("thing");
>> >      f->dump_string("key", iter->first);
>> >      iter->second.dump(f);
>> >      f->close_section();
>> >    }
>> >    f->close_section();
>> >
>> > Admittedly that doesn't come up *that* often, but it happened enough when
>> > we first started doing the dump stuff that all/most of the dumpers work
>> > that way.
>> >
>> > How should we deal with that?
>> >
>>
>> Modify the dumpers to not work this way? It's a matter of conventions,
>> not set in stone. If we keep this form then we'd need to create
>> decoders for these records:
>>
>> struct KeyRecord {
>>   Key k;
>>   Value v;
>>
>>   void decode_json(JSONObj *obj) {
>>     JSONDecoder::decode_json("key", key, obj);
>>     v.decode_json(obj);
>>   }
>> };
>>
>> This will work, assuming Value doesn't have any field indexed by "key".
>
> I think it mostly comes up in dumping other structures, not parsing, so it
> may be mostly ok on that end. I'm not sure how to manage dumping, though,
> if the section is opened inside the value object's dump().
>
> This also worries me: ceph_dencoder does
>
>       jf.open_object_section("object");
>       den->dump(&jf);
>       jf.close_section();
>
> which means that this sort of change will probably the type
> encoding/decode unit tests we run.
>
> Maybe something like
>
> void encode_json(Formatter *f) const
> {
>   f->open_object_section("mytype");
>   dump(f);
>   f->close_section();
> }
>
> will give us the cleaner interface you're looking for.  And the

Yeah.

> ceph-dencoder type definitions can mark types which implement one or the
> other... or we can implement the encode_json one for all objects as a
> wrapper via a macro or something we don't have to duplicate teh
> boilerplate everywhere?
>
That's one option. Not sure if I like the idea of doing it through a
macro, but it's solvable anyway.

Yehuda

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2013-02-07  1:05 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-02-06 22:19 json parsing/decoding Yehuda Sadeh
2013-02-06 22:23 ` Sage Weil
2013-02-06 22:36   ` Yehuda Sadeh
2013-02-07  0:53     ` Sage Weil
2013-02-07  1:05       ` Yehuda Sadeh

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.