cereal
cereal copied to clipboard
Eliminate JSON entry for empty std::optional
This change remove the entry in JSON if what is being serialized/deserialized is of type std::optional<T>, and the value is std::nullopt. When it's not std::nullopt, only the contained value (T) is saved, instead of a flag plus the value.
This is helpful when using JSON as web protocol. People would want to eliminate optional entries when they are empty. This improved human-readability of the JSON protocol and make it easier to interact with other languages.
Seems this is exactly what is requested here: https://github.com/USCiLab/cereal/issues/459
It might be a good idea to make this an option of JSONArchive instead of altering its default behavior?
@WSoptics Changed it to an option and defaults to false. Also added unit tests.
I like the change but can't merge :-)
One thing though however, it seems the option is only available for C++17, which seems not really necessary?
You should probably ping @AzothAmmo to potentially have it merged.
@fiesh std::optional is only available since C++17. You mean keep the option but leave it with no effect?
@AzothAmmo Can we merge this feature?
@fiesh std::optional is only available since C++17. You mean keep the option but leave it with no effect?
Sorry, I somehow thought std::optional was C++11 -- you're right of course!
Would also love to have this behavior in cereal, @AzothAmmo :)
It has been ~1.75 years. @AzothAmmo Do you have any concerns on this PR?
This seems like a good change, and one that makes the output more intuitive and interoperable. Any reason for the delay in merging this in?
@lowsfer it seems this change can't support out-of-order json fileds.
I made a few changes to support out-of-order JSON fields. It works fine in my case, but I don't know if there are any side effects.
here are my changes:
template <class T> inline std::enable_if_t<IsOptional<T>::value>
CEREAL_LOAD_FUNCTION_NAME( JSONInputArchive & ar, NameValuePair<T> & t )
{
if (ar.skippedNullopt())
{
// search node name
if (ar.searchNodeName(t.name))
{
ar.setNextName( t.name );
std::decay_t<decltype(*t.value)> val;
ar( val );
t.value = std::move(val);
}
else
{
t.value = std::nullopt;
}
}
else
{
loadImpl(ar, t);
}
}
const char* searchNodeName(const char* name) const
{
auto currentIterator = itsIteratorStack.back();
const auto len = std::strlen( name );
for (const auto& it : currentIterator)
{
const auto currentName = it.name.GetString();
if( ( std::strncmp( name, currentName, len ) == 0 ) &&
( std::strlen( currentName ) == len ) )
{
return currentName;
}
}
return nullptr;
}
// Iterator support range for syntax
auto begin()
{
return itsMemberItBegin;
}
auto end()
{
return itsMemberItEnd;
}
@yuziqii Would you like to create a commit including your changes? You can create an PR into my forked branch lowsfer:eliminate-empty-optional, then I merge it and your commit will be included in this PR.
@lowsfer Sure, I'd be happy to submit a PR , and I've already done that. Let me know if there's any problems. here is the PR.