A question of interface


D

Daniel

This isn't a question about a language issue, but a question about the design of
an interface, hopefully not entirely off topic as the features of the language and language conventions both constrain design.

Consider a json class, called json, that internally stores an array of values,
or an object of name value pairs, or a bool, string, numeric or null value (the
actual internal representation is unimportant.) Constructors for string, bool,
double and int values can naturally be provided as json(const string& val),
json(bool val), json(double val), and json(int val). Construction of a null
value can use the copy constructor and a json::null prototype.

But what to do about constructing object (collection of name-value pairs) and
array values?

Approach 1:

json obj(json::an_object);
// Constructs a json value of type object from an empty object prototype
// (or equivalently, given an enum type indicator)

obj["first_name"] = "John";
obj["last_name"] = "Smith";

// Any array operations on obj would throw

json arr(json::an_array);

arr.add("John");

// Any object operations on arr would throw

Approach 2:

json obj = json::make_object();

json arr = json::make_array();

Approach 3:

json val;
// Constructs a json object of undefined type (not a valid json state),
// or alternatively a null type (a valid json type), and delay until the first
// operation to fix the type

That is, if the first operation is

val["age"] = 50;

val becomes an object, and subsequent array operations would throw, but if the
first operation is

val.add(50);

val becomes an array, and subsequent object operations would throw.

Approach (3) is I believe the most popular in the open source implementations,
it's probably a little more javascript like, users seem to like its
simplicity, but at the same time the user forums have users who construct

json val;

add no members, and are surprised that

std::cout << val << std::endl;

produces "null" rather than the empty object notation "{}".

Then again, if we take approach (1) or (2), what to do with the default
constructor? Letting it be null type is probably okay, it's desirable to have a
default constructor that's lightweight, but a null default doesn't provide much
useful functionality for the user.

Anyway, if anyone has any thoughts, with consideration to the "principle of
least amazement" and most expected C++ convention, I'd be grateful for feedback.

Thanks,
Daniel
 
Ad

Advertisements

Ö

Öö Tiib

Consider a json class, called json, that internally stores an array of values,
or an object of name value pairs, or a bool, string, numeric or null value (the
actual internal representation is unimportant.) Constructors for string, bool,
double and int values can naturally be provided as json(const string& val),
json(bool val), json(double val), and json(int val). Construction of a null
value can use the copy constructor and a json::null prototype.

There have JsonCpp and JSON Spirit been around for more than half of decade
I trust. Difference between the two feels to be matter of taste. Both have good
ideas and are simple enough to use.
Anyway, if anyone has any thoughts, with consideration to the "principle of
least amazement" and most expected C++ convention, I'd be grateful for feedback.

There are no silver bullet interface conventions because application types differ.
For example such applications where it is needed to serialize objects that contain
pointers to other objects, and those pointers form a graph that might have cycles
or non-trivial joins are rare. However if you have one then you have to consider
how well your marshalling supports that data model.
 
D

Daniel

There have JsonCpp and JSON Spirit been around for more than half of decade
I trust. Difference between the two feels to be matter of taste. Both have
good ideas and are simple enough to use.
Those are the two I'm familiar with, jsoncpp follows Approach 3 mentioned in the
original post (but both have reported flaws that have never been fixed, and
which ruled them out of consideration for a recent project, so I wrote another
one and open sourced it, hence my interest in the interface issues.)
For example such applications where it is needed to serialize objects that
contain pointers to other objects, and those pointers form a graph that might > have cycles or non-trivial joins are rare. However if you have one then you
have to consider how well your marshalling supports that data model.

Somewhat tangential to the original post, but json as a data standard does not
support the notion of a pointer or a reference to other pieces of json, andI
can't see how cycles could arise in implementations, there is no notion of an
implementation storing a pointer to a shared json value.

Thanks for your comments,
Daniel
 
Ö

Öö Tiib

Those are the two I'm familiar with, jsoncpp follows Approach 3 mentionedin the
original post (but both have reported flaws that have never been fixed, and
which ruled them out of consideration for a recent project, so I wrote another
one and open sourced it, hence my interest in the interface issues.)

It is difficult to fix flaws in open source permanently since maintainers soon patch
it into broken again. I usually either fix or strip flawed features and then use my
fork versions. Writing new libs makes inevitably some new defects too so NIH is
usually a bad policy, but YMMV.
have cycles or non-trivial joins are rare. However if you have one then you

Somewhat tangential to the original post, but json as a data standard does not
support the notion of a pointer or a reference to other pieces of json, and I
can't see how cycles could arise in implementations, there is no notion of an
implementation storing a pointer to a shared json value.

JSON supports a tree of objects and/or arrays consisting of bools, numbers
and strings as leaves. We use JSON for serialising some actual application
data that has often far more complex structure and leaves. For example
it may contain possibly shared, possibly circular references. Therefore we
need a conversion layer above JSON lib. For example it emulates references
using above described primitives. That layer above JSON lib is anticipated
to be the prime user of JSON lib's interface.

Why I brought that ?tangential? issue up is because you asked for thoughts
about "principles of least amazement". Minimise amazement and maximise
convenience of the writer of described layer, remove everything that he
does not need ... and you have a winner. ;)
 
W

woodbrian77

It is difficult to fix flaws in open source permanently since maintainerssoon patch
it into broken again. I usually either fix or strip flawed features and then use my
fork versions. Writing new libs makes inevitably some new defects too so NIH is
usually a bad policy, but YMMV.

Do you suspect maintainers are intentionally sabotaging their
libraries in order to get people to pay for "premium,"
non-sabotaged version?

Brian
Ebenezer Enterprises - So far G-d has helped us.
http://webEbenezer.net
 
Ad

Advertisements

Ö

Öö Tiib

Do you suspect maintainers are intentionally sabotaging their
libraries in order to get people to pay for "premium,"
non-sabotaged version?

No; I don't think so. What I think is that the people who write open
source just love to code too much. Sometimes there are quite
questionable feature requests. They try to implement it anyway.
That is sooner or later breaking something. Less is usually more
with code and features but that is sometimes hard to see for a
maker.
 
Ad

Advertisements


Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top