5

According to the Google JSON style guide, it is advisable to remove empty or null values.

When using JsonCpp, how can empty or null values be removed, either from the object structure or when writing to a stream?

I want the following code:

#include <json/json.h>
#include <json/writer.h>

Json::Value json;
json["id"] = 4;
// The "name" property is an empty array.
json["name"] = Json::Value(Json::arrayValue);
Json::FastWriter fw;
std::cout << fw.write(json) << std::endl;

to produce:

{
    "id": 4,
}
Kevin
  • 4,070
  • 4
  • 45
  • 67

3 Answers3

4

You may add a pre-process to remove empty members, something like:

void RemoveNullMember(Json::Value& node)
{
    switch (node.type())
    {
        case Json::ValueType::nullValue: return;
        case Json::ValueType::intValue: return;
        case Json::ValueType::uintValue: return;
        case Json::ValueType::realValue: return;
        case Json::ValueType::stringValue: return;
        case Json::ValueType::booleanValue: return;
        case Json::ValueType::arrayValue:
        {
            for (auto &child : node)
            {
                RemoveNullMember(child);
            }
            return;
        }
        case Json::ValueType::objectValue:
        {
            for (const auto& key : node.getMemberNames())
            {
                auto& child = node[key]
                if (child.empty()) // Possibly restrict to any of
                                   // nullValue, arrayValue, objectValue
                {
                    node.removeMember(key);
                }
                else
                {
                    RemoveNullMember(node[key]);
                }
            }
            return;
        }
    }
}

And so finally:

Json::Value json;
json["id"] = 4;
json["name"] = Json::Value(Json::arrayValue); // The "name" property is an empty array.
RemoveNullMember(json); // Or make a copy before.
Json::FastWriter fw;
std::cout << fw.write(json) << std::endl;
Jarod42
  • 203,559
  • 14
  • 181
  • 302
1

Personally I'd prefer an option in the writer which allows to filter out empty/null properties when writing. Thereby, one could define an own class like class MyFastWriter : public FastWriter, override printValue for handling type objectValue accordingly and call FastWriter::writeValue for the rest. Unfortunately, JsonCpp API has defined member function printValue as private, such that you cannot override it (and not even call it) from a custom derived class.

Hence, I see only three principal ways to achieve what you want: (1) Adapting the json value before writing, (2) defining an own writer class and copying a lot of code from FastWriter, or (3) change the source code of FastWriter.

There is already a proper answer for option (1) provided by Jarod42.

Option (2) and (3) share the major drawback that you copy or alter implementation details which might change in future versions of JsonCpp; But still, if one is very aware of the drawbacks coming with altering or copying a library's source code, it might be an option. A situation might be that the json value at hand shall keep empty properties, is very large, and has to be written rather often; then it becomes unhandy to copy the value, altering it just for writing, and writing it then again and again.

I'm for sure not a friend of altering source code; Anyway, see the following adapted version of FastWriter::writeValue which achieves the output you want:

void FastWriter::writeValue(const Value& value) {
  switch (value.type()) {

  // cases handling the other value.types remain as is...
  ...

  // case handling objectValue is adapted:
  case objectValue: {
    Value::Members members(value.getMemberNames());
    document_ += '{';

    // inserted flag indicating that the first element is to be written:
    bool isFirst = true;

    for (Value::Members::iterator it = members.begin(); it != members.end();
         ++it) {

      const std::string& name = *it;

      // inserted to skip empty/null property values
      if(value[name].empty() || value[name].isNull())
          continue;

//    Replaced: necessary because the first written entry is not necessarily members.begin:
//      if (it != members.begin())
//        document_ += ',';
      if (!isFirst)
          document_ += ',';
      else
          isFirst = false;

      // Kept as is...            
      document_ += valueToQuotedStringN(name.data(), static_cast<unsigned>(name.length()));
      document_ += yamlCompatiblityEnabled_ ? ": " : ":";
      writeValue(value[name]);
    }
    document_ += '}';
  } break;
  }
}
Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58
  • 1
    Notice that depending of the wanted definition of *empty*, recursive case would be more complicated `{"a":"a", "empty_rec":{"empty":null}}`. – Jarod42 Mar 12 '17 at 17:22
0

I'm assuming that the values you're setting are not constant values and you're saving data from a class or some other data structure. In this case, you can simply check the data in C++ side and skip json["varName"] part completely.

Whatever you put in a JSON file is going to be in the final JSON because you're setting that field in the JSON file to something. As you said, it is advisable to not include NULL / empty values but it's not a must. NULL, empty or default values are still values that some people might want in their JSON file specifically to show that particular entity doesn't have that entry but it is still a field in that data.

I'd use the empty array as is for the name field in your JSON file, this way the reader can say that "Oh ok, this object doesn't have any name values to it" and if that wasn't supposed to happen or if it feels odd, even non-technical clients would be able to guide you through it and debugging would be much simpler. Unless this is a part of a networking code and you need to have the fastest response times. Otherwise, just include it.

Golden rule of debugging: Thich files save lives.

gamiseta
  • 85
  • 7