-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Improve checked json casting #10087
Improve checked json casting #10087
Conversation
This introduces new utility functions to get elements from JSON - with nice error messages, if the expected type does not match.
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.
Thank you for your efforts so far! This is exactly what we were thinking of, the direction is perfect!
I encountered some problems using the new implementations in
derivations.cc
[...] where there is no cast possible [...]
Ah yes. Here, introducing more type safety broke the type checks, because there is a cast defined from nlohmann::json &
to std::set<std::string>
(and basically anything else that can be represented as a JSON), but none from nlohmann::json::array_t &
to std::set<std::string>
.
To fix this, you have to define additional functions that validate the required nested types properly. If you want to have a go at this for yourself, these are the function definitions:
StringSet getStringSet(const nlohmann::json & value);
Strings getStringList(const nlohmann::json & value);
StringMap getStringMap(const nlohmann::json & value);
(You also need to add #include "types.hh"
at the top of json-utils.hh
, else you'll get "Cannot overload functions by return type alone" errors when trying to add the definition to json-utils.cc
)
If you get stuck or you'd rather use a finished solution, check iFreilicht@bddf5c5.
A few more notes on things you can improve:
Now that there's a proper replacement for ensureType
, you should update the valueAt
function to take an nlohmann::json::object_t
instead of a nlohmann::json
. This makes it type safe, and you can remove the disclaimer about it not checking the type of its input, as the compiler will not allow passing anything in that wasn't checked by getObject()
before.
Additionally, you should run the functional tests and see if any of them broke. Potentially, you'll have to adapt the tests to check for the updated error message.
It would be even cooler if you could also add unit tests for your new functions, as nix is very under-tested currently. The tests for this file are in tests/unit/libutil/json-utils.cc
.
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/2024-03-04-nix-team-meeting-minute-130/40830/1 |
Hello again! Sorry this took so long. I had everything implemented, then realized (bless the tests) that somehow the program segfaulted. Should be fixed now :D (I hope this doesn't introduce new bugs ^^') The last place where
|
No worries! We're doing this in our free time, I understand that it can take a few days to get a response. Hmm, segfaults would be a bit problematic. However, I couldn't reproduce it. Which test did you run that gave you that error? If returning references causes segfaults, then the functions shouldn't do that, and return values instead. I don't know if this is problematic in terms of performance (could even be beneficial), but I think for the current use-case, we don't have to worry about that, as the files we parse are pretty small. Interesting side-note here: I found out that you can skip
So maybe,
That seems unnecessary. I feel like this is a super-specific use-case that doesn't relate to JSON in general. I would suggest to just write a proper parsing routine directly within that function, something like this:
I didn't try this out or test it at all, and there may be better names for those variables, but I think it shows the basic idea.
I think you may have forgotten to update the type of the implementation (or of the definition). You have to update the type in both the header and the source file, otherwise you'll get errors like that. That's how I was able to reproduce it. BTW, another thing I noticed: It would be better to actually implement all the basic
|
Thank you for the quick response. Regarding the segfault I have created a separate branch Making I will update the implementation and write the unit tests as soon as possible and then get back to you :D |
Hello again @iFreilicht! I tried to progress but came across more problems that I don't understand fully. The problems start when you try to access more complex JSON structures like nested object or arrays. Then come the segfaults and/or Sadly my time right now is pretty limited because of exams, so I can't try out as much as I wish - If you have the time I would be very thankful if you maybe could point me to some direction (e.g. regarding the question whether we should even return references) ^^' In any case many thanks and I wish you a great weekend!!! |
Hey @haenoe, thank you for your continued efforts, I'll have a look as soon as possible. Best of luck on your exams! |
Alright, I had a look now, and I found a fix! The issue is typical C++ nonsense, and I pushed the fix to a fork of your repo, so let's look at it. First off, haenoe/json-questions@cdb7da0 fixes your use of Secondly, haenoe/json-questions@e081b14 fixes the memory access errors, simply by adding an additional overload for the
The reason why the original definition is not enough is very subtle. This is just my theory, I'm not an expert in C++ and I feel this is going way over my head. Let's look at it again:
This works when calling it with an
It seemingly also works when calling it with an
However, this call actually has a lot more going on than you might think. Because Anyway, when that new Adding the new overload for What to do now
As for you, you just have to add a similar implementation of |
Hello again! I'll try to work on the fix and the rest of the unit tests asap. |
Hello! After writing most of the test this feels really refreshing -- as everything works as it should without crashing :D Should I look for a way to make the tests less repetitive? Thank you |
src/libutil/json-utils.cc
Outdated
std::optional<nlohmann::json> getNullable(const nlohmann::json & value, const std::string & key) | ||
{ | ||
try { | ||
auto & v = valueAt(value, key); | ||
return v.get<nlohmann::json>(); | ||
} catch (...) { | ||
return std::nullopt; | ||
} | ||
} |
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.
Ah this is different thing than the one I indented, which would turn null
to std::nullopt
and anything else to std::optional { <original> }
.
This one is useful too, but maybe lets call it optionalValueAt
or something. "nullable" implies null
but this one isn't about null
at all! Just about potentially-missing fields.
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.
Also, I hope we will transfer from having fewer sometimes-there-sometimes-missing fields, and more explicit null
s, so this function is hopefully just used for things we don't control and some backwards compat.
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.
Ah okay -- makes sense! Thanks :D
src/libutil/json-utils.cc
Outdated
std::optional<nlohmann::json> getNullable(const nlohmann::json & value, const std::string & key) | ||
{ | ||
try { | ||
auto & v = valueAt(value, key); | ||
return v.get<nlohmann::json>(); | ||
} catch (...) { | ||
return std::nullopt; | ||
} | ||
} |
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.
Also, I hope we will transfer from having fewer sometimes-there-sometimes-missing fields, and more explicit null
s, so this function is hopefully just used for things we don't control and some backwards compat.
src/libfetchers/git.cc
Outdated
publicKeys = publicKeysJson.get<std::vector<PublicKey>>(); | ||
auto pubKeys = getArray(publicKeysJson); | ||
publicKeys.clear(); | ||
for (auto jsonKey : pubKeys) { | ||
auto keyObj = getObject(jsonKey); | ||
auto type = getString(getNullable(keyObj, "type").value_or("ssh-ed25519")); | ||
auto key = getString(valueAt(keyObj, "key")); | ||
publicKeys.push_back({ type, 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.
We shouldn't inline things like this. Instead we can change the JSON deserializer for PublicKey
to use the new combinators. (And we could test that deserializer too.)
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 that I understand that correctly this means implementing a new static method for PublicKey::fromJSON
? ^^'
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.
Grep for JSON_IMPL
. We want to keep that pattern.
Also, How did it work before? If each on was a string, it should stay that way (at least for now). This PR shouldn't be changing behavior.
Hi, I tried to apply all of the recommendations :D
As always -- many thanks ^^' |
src/libfetchers/fetchers.hh
Outdated
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(PublicKey, type, 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.
Oh sorry, I didn't see this! With this we don't need JSON_IMPL
because we already have a JSON impl.
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 thought we needed the new (custom) JSON Impl for the improved error messages (by using getString
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.
@haenoe That might be a great follow-up task (and you are welcome to submit it as a separate PR!), but it should not be part of this PR, which should be a "pure" refactor --- without any behavioral changes.
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.
@haenoe Oh wait... I am being stupid. It is a non-behavioral change! Still it might be better as a (quick easy) follow-up PR.
Yes, please do! We can create it in this PR, and then ensure in the next one where you redo that serializer that no behavior is changed. |
Hopefully this addresses everything and I didn't miss anything when creating the |
@iFreilicht @Ericson2314 thank you for being so kind and welcoming throughout this whole process and investing your time. I hope I can contribute more in the future :'D |
Motivation
This introduces new utility functions to get elements from JSON - with nice error messages, if the expected type does not match, as discussed in #9994.
Right now I only implemented a first set of functions, namely for
getString
,getInteger
andgetBoolean
,getArray
andgetObject
, because I'm a bit unsure if I'm on the right track @iFreilicht envisioned.Moreover I encountered some problems using the new implementations in
derivations.cc
(here, here and here) where there is no cast possible fromobject_t
/array_t
to the desired types (e.g.StringSet
/std::set<std::string>
).I would be grateful for any help in that regard, as I sadly don't have much C++ experience and the error messages are rather cryptic - many thanks!
Context
See #9994
Priorities and Process
Add 👍 to pull requests you find important.
The Nix maintainer team uses a GitHub project board to schedule and track reviews.