jbeder / yaml-cpp

A YAML parser and emitter in C++
MIT License
4.91k stars 1.77k forks source link

is it possible to have a partial fallback on struct custom types? #1244

Open Raffaello opened 7 months ago

Raffaello commented 7 months ago

for e.g. given https://github.com/jbeder/yaml-cpp/wiki/Tutorial#converting-tofrom-native-data-types

namespace YAML {
template<>
struct convert<Vec3> {
  static Node encode(const Vec3& rhs) {
    Node node;
    node.push_back(rhs.x);
    node.push_back(rhs.y);
    node.push_back(rhs.z);
    return node;
  }

  static bool decode(const Node& node, Vec3& rhs) {
    //if(node.size() != 3) {
    //  return false;
   // }

    rhs.x = node["x"].as<double>();
    rhs.y = node["y"].as<double>();
    rhs.z = node["z"].as<double>();
    return true;
  }
};
}

would it be possible to have z as optional so the last decode line would be something like:

rhs.x = node["x"].as<double>();
rhs.y = node["y"].as<double>();
rhs.z = node["z"].as<double>(0.0); // note the fallback here

in this case instead of hard-coding a 0.0 would it be possible to pass a value as a fallback?

rhs.x = node["x"].as<double>();
rhs.y = node["y"].as<double>();
rhs.z = node["z"].as<double>(z_fallback);

and when using it to decode a YAML document that for e.g. has only x and y. is it possible to specify the Z fallback, kind of:

{
// without any fallback as x,y,z are present
YAML::Node node = YAML::Load("start: {x:1, y:3, z:0}");
Vec3 v = node["start"].as<Vec3>(); 
}
{
// z fallback
YAML::Node node = YAML::Load("start: {x:1, y:3}");
Vec3 v = node["start"].as<Vec3>({.z=z_fallback});  // here there is no way to pass a "partial fall back" as i am aware of.
}

a workaround for this partial fallback could be: when decoding, use a special value to indicate fallback has happened, so after conversion with .as, check that special value and replace with the right fallback. But this requires a value to not be used and be reserved. not always possible.

another one is to use a global value for the fallback to change as needed before the .as call.


Is there any undocumented way to achieve such a partial fallback "elegantly"? Would it make any sense from a yaml-cpp perspective to allow decoding having partial fall-back on a structured type?

Would it be possible to add in the convert template such kind of partial fallbacks functionalities? like storing one T to be used as fallback, that can be overridden when calling the higher level .as with fallback that is passing it to template struct convert so it will use those as fallbacks? or something like that?

Raffaello commented 7 months ago

a kind of make it working is:

namespace YAML {
template<>
struct convert<Vec3> {
  static double z_fallback; // in the .cpp file declare the default value
  static Node encode(const Vec3& rhs) {
    Node node;
    node.push_back(rhs.x);
    node.push_back(rhs.y);
    node.push_back(rhs.z);
    return node;
  }

  static bool decode(const Node& node, Vec3& rhs) {
    //if(node.size() != 3) {
    //  return false;
   // }

    rhs.x = node["x"].as<double>();
    rhs.y = node["y"].as<double>();
    rhs.z = node["z"].as<double>();
    return true;
  }
};
}

its .cpp file

#include <myconvert.h>
double convert<Vec3>::z_fallback = 0.0;

then when using it:

// z fallback
YAML::Node node = YAML::Load("start: {x:1, y:3}");
YAML::convert<Vec3>::z_fallback = 1.0;
Vec3 v = node["start"].as<Vec3>();

this works only of course if z is optional parameter. (unless using a reserved value to mark as fallback happened, and report z missing etc..) ok this leads to have a static bool, defining if z_fallback should happen or not.. so yeah. it might be enough as it is for yaml-cpp without any other modifications.

(i am leaving the issue open just for the sake of an eventual discussion, but feel free to close it too)

Pentamix commented 6 months ago

The into() feature requested in #1073 would seem to cover this. There, into() is used instead of as(). into() doesn't modify the destination variable if the key doesn't exist in the yaml. In this case, you would preset your destination variable to the default value, then into() will overwrite it if the key exists in the yaml, and leave it untouched otherwise. If you agree this would work for you, then please add a vote there to encourage its inclusion.