danielaparker / jsoncons

A C++, header-only library for constructing JSON and JSON-like data formats, with JSON Pointer, JSON Patch, JSON Schema, JSONPath, JMESPath, CSV, MessagePack, CBOR, BSON, UBJSON
https://danielaparker.github.io/jsoncons
Other
721 stars 163 forks source link

support for failing ```decode_json``` #181

Closed rbroggi closed 5 years ago

rbroggi commented 5 years ago

Hi all,

First of all thank you for the amazing work here. I really enjoy using this lib. After using it for a while, I've started using the JSONCONS_MEMBER_TRAITS_DECL Macro for serializing / deserializing custom objects. I love this approach and I think this is the closest cpp can get from languages with reflection behavior for serialization/deserialization functions.

Back to the issue, I've noticed that if I specify a json string with a subset of fields that an object is supposed to have, the jsoncons::decode_json method will not have an option for failing.

Here below you can see a sample piece of code of what I mean:

#include <cassert>
#include <iostream>
#include <jsoncons/json.hpp>

namespace jsTest {

  class Person {
  public:
    Person(const std::string &name, const std::string &surname,
           const std::string &ssn, unsigned int age)
        : name(name), surname(surname), ssn(ssn), age(age) {}
    Person() {}

    bool operator==(const Person &rhs) const {
      return name == rhs.name && surname == rhs.surname && ssn == rhs.ssn &&
             age == rhs.age;
    }
    bool operator!=(const Person &rhs) const { return !(rhs == *this); }

  private:
    // Make json_type_traits specializations friends to give accesses to private members
    JSONCONS_TYPE_TRAITS_FRIEND;

    std::string name;
    std::string surname;
    std::string ssn;
    unsigned int age;
  };
}

// Declare the traits. Specify which data members need to be serialized.
JSONCONS_MEMBER_TRAITS_DECL(jsTest::Person, name, surname, ssn, age)

int main()
{
  //Incomplete json string: field ssn missing
  std::string s3 = R"({"name":"Rod","surname":"Bro","age":30})";
  //This method should provide some kind of info if not all the fields
  // of jsTest::Person are found in the string
  auto rod = jsoncons::decode_json<jsTest::Person>(s3);

  //Encode again after serialization (now the ssn will be dumped but with empty string - default initialization of the field)
  std::string s4;
  jsoncons::encode_json(rod, s4, jsoncons::indenting::indent);
  std::cout << s4 << "\n";
}

There is no way of distinguishing the decoding of the json string:

{"name":"Rod","ssn":"BRG",surname":"","age":30}

and the json string:

{"name":"Rod","ssn":"BRG","age":30}

Maybe would be nice to be able to distinguish between the mentioned cases.

The use-case I'm trying to apply it is to deserialize a json string into a User-defined type and:

  1. Faild deserialization (decoding) every time a mandatory field of the User-defined type is missing in the json string.
  2. Complete deserialization (decoding) if only optional fields of the User-defined type are missing in the json string,

Thank you again for the great product, best regards, Rodrigo Broggi

danielaparker commented 5 years ago

Hi Rodrigo,

One option would be to use JSONCONS_GETTER_CTOR_TRAITS_DECL instead. Then you would have

namespace jsTest {

    class Person {
    public:
        Person(const std::string& name, const std::string& surname,
               const std::string& ssn, unsigned int age)
            : name_(name), surname_(surname), ssn_(ssn), age_(age) {}

        const std::string& name() const {return name_;}
        const std::string& surname() const {return surname_;}
        const std::string& ssn() const {return ssn_;}
        unsigned int age() const {return age_;}

        bool operator==(const Person &rhs) const {
            return name_ == rhs.name_ && surname_ == rhs.surname_ && ssn_ == rhs.ssn_ &&
                age_ == rhs.age_;
        }
        bool operator!=(const Person &rhs) const { return !(rhs == *this); }
    private:
        std::string name_;
        std::string surname_;
        std::string ssn_;
        unsigned int age_;
    };
}

// Declare the traits. Specify which data members need to be serialized.
JSONCONS_GETTER_CTOR_TRAITS_DECL(jsTest::Person, name, surname, ssn, age)

int main()
{
    try
    {
        //Incomplete json string: field ssn missing
        std::string s3 = R"({"name":"Rod","surname":"Bro","age":30})";
        auto rod = jsoncons::decode_json<jsTest::Person>(s3);
    }
    catch (const std::exception& e)
    {
        std::cout << e.what() << "";
    }
}

Output:

Key 'ssn' not found

But if you prefer to define the traits in terms of the data members, I've added another macro on master, JSONCONS_STRICT_MEMBER_TRAITS_DECL, which will do that, and throw if a member is missing.

Finally, fairly soon we'll have support for Concise data definition language (CDDL), which will allow validation of JSON (or CBOR, UBJSON etc.) by passing an optional CDDL specification to decode_json (or decode_cbor, decode_ubjson, etc), and which will give you line and column information in the input where validation failed (such as for a missing mandatory member. )

Best regards, Daniel

rbroggi commented 5 years ago

Hi Daniel,

Thank you for the prompt answer and for the new MACRO. JSONCONS_GETTER_CTOR_TRAITS_DECL indeed feels more elegant to me specially because I can get rid of the friend MACRO.

I will be in the watch for the CCDL feature, it will probably resolve all of my problems/requirements.

Thank you in the meanwhile, Rodrigo

rbroggi commented 5 years ago

Hi Daniel again,

I know it might not be easy to determine 'timelines' but since I've been considering about using the lib within my organization package distribution (and there is a lot of bureaucracy to include it there), I would love to have a rough idea (weeks/months/years) on how brief the CDDL feature would be delivered so that I can decide to either:

  1. Ask for the inclusion of the current latest release;
  2. Wait for the functionality for asking for that version inclusion;

Thank you very much and best regards,

Rodrigo

danielaparker commented 5 years ago

Not weeks, not years, but other than that, hard to say!

Best regards, Daniel

rbroggi commented 5 years ago

Thank you Daniel