-
-
Notifications
You must be signed in to change notification settings - Fork 728
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add policy hooks to std.json to support custom extensions #6059
Conversation
Thanks for your pull request, @marler8997! We are looking forward to reviewing it, and you should be hearing from a maintainer soon. Some tips to help speed things up:
Bear in mind that large or tricky changes may require multiple rounds of review and revision. Please see CONTRIBUTING.md for more information. Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. |
std/json.d
Outdated
foreach (ref member; members) | ||
if (name == member.name) | ||
return member.value; | ||
assert(0, "JSON object does not contain member named: " ~ name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enforce for external / environmental errors
std/json.d
Outdated
struct JSONValue | ||
alias JSONValue = JSONValueTemplate!DefaultPolicy; | ||
|
||
private struct JSONValueTemplate(alias Policy) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not private
std/json.d
Outdated
ref auto opIndex(in char[] name) const | ||
{ | ||
foreach (ref member; members) | ||
if (name == member.name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing indent
std/json.d
Outdated
return member.value; | ||
assert(0, "JSON object does not contain member named: " ~ name); | ||
} | ||
ref auto opIndexAssign(Value value, string name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ref auto => u probably wanna return *this ?
or how about using same signature as in std.json:
pure void opIndexAssign(ref Value value, string key);
std/json.d
Outdated
alias Value = typeof(this); | ||
static if (Policy.preserveMemberOrder) | ||
{ | ||
struct JSONObject |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optional: this is basically an AA with ordered fields; it's re-usable; why not expose it as a generic:
struct AAOrdered(K,V){
...
}
2fb8b15
to
e6f1e64
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.
std/json.d
Outdated
enum preserveMemberOrder = true; | ||
} | ||
|
||
struct OrderedAA(K,V) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move to std.array? (eg has assocArray etc)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.
std/json.d
Outdated
} | ||
private Member[] members; | ||
size_t length() const { return members.length; } | ||
inout(V)* opIn(in char[] name) inout |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
char[] instead of K ? (do we need specialization for case K==string to allow char[] in that case?)
=> a unitest with orderedAA!(int, double) would've caught this bug
std/json.d
Outdated
return &member.value; | ||
return null; | ||
} | ||
ref auto opIndex(in char[] name) const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
std/json.d
Outdated
return member.value; | ||
assert(0, "OrderedAA does not contain member named: " ~ name); | ||
} | ||
void opIndexAssign(V value, K name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/name/key/ everywhere in this struct
std/json.d
Outdated
foreach (ref member; members) | ||
if (name == member.name) | ||
return member.value; | ||
assert(0, "OrderedAA does not contain member named: " ~ name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enforce!JSONException ?
std/json.d
Outdated
{ | ||
foreach (name; names) | ||
foreach (member; obj.members) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
foreach (key, value; obj) {...}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, in the DefaultPolicy case, names
is just an array of strings, not an associative array.
std/json.d
Outdated
() @trusted { store.object = aa; }(); | ||
} | ||
} | ||
else static if (Policy.preserveMemberOrder && is(T == JSONObject)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that seems restrictive compared to other policy which didn't only accept JSONObject but also things that converted to it via Key : string
and Value(value)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe. It seems a bit odd to take something that is unordered and then copy it to something that is ordered. Not sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An application could always do the conversion themselves to. This is something that doesn't have to be implemented in the library (AFAICT).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, it could be AAordered!(Key, Value) with Key and Value convertible to destination types
std/array.d
Outdated
foreach (ref member; array) | ||
if (key == member.key) | ||
return member.value; | ||
assert(0, "OrderedAA does not contain value with key: " ~ key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
~ key
will fail with non-string types- unittest to catch that eg OrderedAA!(int, double)
- not sure if assert is appropriate; it probably should be same type of error as corresponding error in an associative array
std/array.d
Outdated
K key; | ||
V value; | ||
} | ||
private KeyValue[] array; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is that more efficient with Appender!KeyValue
?
std/array.d
Outdated
|
||
struct OrderedAA(K,V) | ||
{ | ||
struct KeyValue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static struct? to avoid hidden __this pointer
f52621e
to
3d33c71
Compare
This seems like a misapplication of JSON. JSON objects are designed to be unordered; from the spec:
My inclination is that if what you're doing with JSON requires you to keep the same order of keys, then there's something wrong with your design. |
My main hesitation with this feature is that it doesn't conform to the JSON spec. It's one thing to make it possible for user code to do whatever it wants, which is why I gravitate towards a SAX-style / stream-based parser, because that enables user code to implement order-dependence without actually explicitly doing that in Phobos. But it's another thing for Phobos to explicitly support something that, strictly speaking, doesn't follow the JSON spec. I'm all for enabling the user, but I'm not a fan of vendor-specific extensions (which is basically what this PR is) that eventually leads to fragmentation. And just to be clear, I'm not complaining merely because I like being pedantic about spec conformance; the underlying issue is that we're trying to impose order upon a structure that has been explicitly defined to be unordered. This, to me, is a code smell. In my experience, this style of coding often has or leads to fragility, unhandled corner cases, exceptional behaviours, and lots of little crufty things like that, that are symptomatic of an inherent impedance mismatch between what the code does and what the data is supposed to represent. I don't feel strongly enough about |
@quickfur thanks for explaining yourself, I see where you are coming from now. I'm thinking a better solution might be to provide a mechanism to allow the user to override the type used for |
Pinging @quickfur and @JackStouffer I've refactored the change. Now it allows you to override the default type for JSON objects, but does not provide any of these types. This allows an application to customize the parser to their needs without the parser providing any non-spec functionality out of the box. Note that I've included the |
2773aee
to
185dcce
Compare
std/json.d
Outdated
{ | ||
auto o = this.objectNoRef; | ||
return *enforce!JSONException(k in o, | ||
"Key not found: " ~ k); | ||
static if (hasMember!(Policy, "ObjectType")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DRY:
static if (hasMember!(Policy, "ObjectType"))
auto ok=o.opIn(k);
else
auto ok=k in o;
return *enforce!JSONException(ok, "Key not found: " ~ k);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just discovered that opIn_r
was a thing...now that I know about it, this code only has the one case now :)
std/json.d
Outdated
private struct DefaultPolicy | ||
{ | ||
enum sortObjectMembers = true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can simplify a lot the rest of the code by always requiring a template ObjectType to Policy:
private struct DefaultPolicy
{
enum sortObjectMembers = true;
alias ObjectType(Value)=Value[string];
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which code can be simplified?
std/json.d
Outdated
@@ -1110,6 +1165,91 @@ if (isInputRange!T && !isInfinite!T && isSomeChar!(ElementEncodingType!T)) | |||
assert(json["key1"]["key2"].integer == 1); | |||
} | |||
|
|||
version(unittest) | |||
{ | |||
struct OrderedAA(Key, Value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think others (not me...) were concerned about exposing json with ordered_fields, but I didn't see objections to exposing OrderedAA in std.array; as you currently wrote it by hiding OrderedAA inside a unittest block, this'll require someone who wants to use json with ordered_fields to reimplement OrderedAA
below, which is not DRY and much worse than exposing json with ordered_fields.
In short: using json with ordered_fields should be as simple as:
import std.array:OrderedAA;
static struct OrderedAAPolicy
{
alias ObjectType = OrderedAA;
enum sortObjectMembers = false;
}
auto json1 = parseJSONTemplate!OrderedAAPolicy(...);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. However, now that std.json
does not depend on having OrderedAA
defined, these can be 2 separate changes. When I make a separate PR to add OrderedAA
to std.array
, we can remove this instance I've included in the unittest and swap it out for that.
Note that by separating these changes into distinct PRs, it makes this one easier to accept on it's own. Once this one gets accepted, we can start using this feature in dmd
by simply including our own definition of OrderedAA
until we can add it to std.array
. I'm guessing that adding OrderedAA
is going to be a fair amount of work with lots of revision and documentation so I would rather not have this change depend on that one.
BTW here's what the ECMA15 spec says - it requires insertion order to be preserved (it didn't before).
Granted, JS and JSON are different things, but people mix them up often enough, s.t. preserving insertion order is an interesting use case. |
Can't say I'm surprised by this change to ECMA15 :) Based on @quickfur 's comments I refactored this change to remove the option to preserve order, maybe I should bring it back in? I've currently been working on modifying this PR to appease @quickfur but after I refactored the change based on his comments he has been silent. This seems to be a pattern among some of dlang's developers and definitely not a good one. |
@marler8997 Please understand that (1) I'm not paid for doing this; (2) I'm doing this entirely out of my free time; (3) my free time is very scant (my weekends are usually completely booked); (4) Phobos has tons of other PRs and things that I can look at; and (5) I'm not under contract to respond to stuff by some deadline. Basically, the only reason I even respond at all is only out of goodwill. So I don't understand why a pause of 4 days is unacceptable. If you paid me to work on this full-time, then I would respond within a couple of hours, but IMO the accusatory tone in your comment is uncalled for. If you feel the response rate of Phobos devs is not up to your standard, then perhaps you should consider donating to the D foundation to hire full-time programmers to work on this. |
Sorry for offending you. I don't mean to say that you NEED to be more responsive in general. My point was that if you decide to engage in a PR with criticism that you should follow through when the person takes your criticism and spends their time refactoring their work based on your comments. Of course you're not under any obligation to respond quickly, but I do think it's inconsiderate to criticize someone else's work and then fail to respond in a timely manner after they've gone out of their way to redo all their work based on that criticism. |
Well, your last change was 3 days ago, and as I said, my weekend are booked, so if you subtract that, that's really only 1 day. I don't think that's unreasonable at all, given that there's a whole bunch of other stuff I was reviewing, and my 1 day is really a lot less since I have a full-time job and can't be spending all day working on Phobos -- my employer would fire me. If I didn't respond for a week, then you may have a point, but if your expectations are that I have to respond instantly, then perhaps I should refrain from reviewing your PRs in the future since I can't keep up with your expectations. |
@quickfur I'll know in the future to expect it to take up to a week for you to respond. Glad we've got that cleared up. Let's continue the more useful discussion at hand, namely, what to do with this PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, this is a really big change. I'm going to have to defer to @andralex and the other reviewers whether or not to go with this.
My main concern is that this is a lot of change adding a lot of complexity just to support one use case (support the ability to swap in a different implementation of JSONObject
). Surely there's a simpler way to implement this that doesn't require a changeset of this size? E.g., if JSONObject
were made an interface
instead, so that different concrete types can be hooked in without a renaming that affects everything else in JSONValue
. Then it would just be a matter of having the user supply a factory method of some sort to construct the desired object type. There are probably other ways to implement this that doesn't entail such a massive change.
With a change this big for such a small feature, I'm afraid that even if I were to approve this PR, @andralex is likely to veto it.
alias MyObjectType(K,V) = OrderedAA!(K,V); | ||
} | ||
alias MyJSONValue = JSONValueTemplate!MyPolicy; | ||
------------ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The example here really should be in a ddoc'd unittest. And it should show how to actually use JSONValueTemplate
, not just in how to instantiate it, but how to use it with the rest of the module, e.g., to configure it with a different JSONObject
type, etc..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Was going to wait to fully document until a general "approved" consensus was reached.
I'm going to have to disagree on the size of this change. Most of the lines the actual change are unit tests/documentation and renaming Also, saying that this only adds support for "one use case' is just not true. The change is providing a couple hooks to override to parts of the implementation, along with a framework for providing more hooks in the future. As for @andralex, he may or may not approve but I'd be more interested in every one giving their own opinion rather than people trying to guess what others might think. |
OK, then let's let @andralex decide whether or not to go ahead with this. |
On a cursory look I see two issues with this:
It don't see how this can be brought to a solid addition. |
This is an exploration on enhancing
std.json
. The idea is to add support to preserve order of object members. An example of where this is useful is if you'd like to parse some JSON and then later write it somewhere, but you'd like to maintain the original order of the object members.Note that the techniques used in this PR could also be reused for adding other optional features. It allows the application to pass in a Policy structure that can enable/disable features. If you wanted to add another feature, say, "enableComments", this could be added as a Policy setting as well. This technique could also be ported to the upcoming
std.data.json
.Preserving field order is beneficial for any kind of tool that modifies JSON files. You could imagine tools that modify
dub.json
files, DMD JSON output files, sublime/vscode config files. If a tool like this couldn't guarantee preserving the order of object fields, then this would make the tools much less usable.NOTE: This PR is not fully documented. The work to fully document is pending approval that this is a desirable enhancement.