umn-drone / project / libs / transit / include / util / json.h
json.h
Raw
#ifndef UTIL_JSON_H_
#define UTIL_JSON_H_

#include <functional>
#include <iostream>
#include <initializer_list>
#include <string>
#include <utility>

#include "WebServer.h"
#include "picojson.h"

class JsonObject;
class JsonArray;

/**
 * @class Manages a picojson::value, provides implicit casting for valid JSON types.
 */
class JsonValue {
public:

    /**
     * @brief Default constructor
     */
    JsonValue() : v() {};

    /**
     * @brief Create a JsonValue from an existing picojson::value (creates a copy)
     * @param val: picojson:value to copy
     */
    JsonValue(picojson::value val) : v(std::move(val)) {}

    /**
     * @brief Create a JsonValue from a double
     * @param d: a double
     */
    JsonValue(double d) : v(d) {}

    /**
     * @brief Create a JsonValue from an int
     * @param i: an int
     */
    JsonValue(int i) : v(static_cast<double>(i)) {}

    /**
     * @brief Create a JsonValue from a string
     * @param s: a string
     */
    JsonValue(const std::string& s) : v(s) {}

    /**
     * @brief Create a JsonValue from a string literal
     * @param s: a string literal
     */
    JsonValue(const char* s) : v(s) {}

    /**
     * @brief Create a JsonValue from a bool
     * @param b: a bool
     */
    JsonValue(bool b) : v(b) {}

    /**
     * @brief Create a JsonValue from a JsonObject
     * @param o: a JsonObject
     */
    JsonValue(const JsonObject& o);

    /**
     * @brief Create a JsonValue from a JsonArray
     * @param a: a JsonArray
     */
    JsonValue(const JsonArray& a);

    /**
     * @brief Destructor
     */
    ~JsonValue();

    /**
     * @brief Copy constructor
     * @param other: another JsonValue
     */
    JsonValue(const JsonValue& other);

    /**
     * @brief Copy operator
     * @param other: another JsonValue
     * @return this
     */
    JsonValue& operator=(const JsonValue& other);

    /**
     * @brief Move constructor
     * @param other: another JsonValue
     */
    JsonValue(JsonValue&& other);

    /**
     * @brief Move operator
     * @param other: another JsonValue
     * @return this
     */
    JsonValue& operator=(JsonValue&& other) noexcept;

    /**
     * @brief Create a a JsonValue using a reference to an existing picojson::value.
     * Instead of creating a copy of the picojson::value, this just stores a reference to it.
     * This is only needed for internal use.
     * @param v: a picojson::value to create a reference to
     * @return the new JsonValue
     */
    static JsonValue FromReference(picojson::value& v);

    /**
     * @return The underlying picojson::value
     */
    picojson::value& GetValue() { return isRef ? ref.get() : v; }

    /**
     * @return The underlying picojson::value
     */
    const picojson::value& GetValue() const { return isRef ? ref.get() : v; }

    // DOUBLE

    /**
     * @return Converts this to a double
     */
    operator double() const { return Get<double>(); }

    /**
     * @brief Set this value to a double
     * @return this
     */
    JsonValue& operator=(double d) { Set(d); return *this; }

    // FLOAT

    /**
     * @return Converts this to a float
     */
    operator float() const { return static_cast<float>(Get<double>()); }

    /**
     * @brief Set this value to a float
     * @return this
     */
    JsonValue& operator=(float f) { Set(static_cast<double>(f)); return *this; }

    // INT

    /**
     * @return Converts this to an int
     */
    operator int() const { return static_cast<int>(Get<double>()); }

    /**
     * @brief Set this value to an int
     * @return this
     */
    JsonValue& operator=(int i) { Set(static_cast<double>(i)); return *this; }

    // STRING

    /**
     * @return Converts this to a string
     */
    operator std::string() const { return Get<std::string>(); }

    /**
     * @brief Set this value to a string
     * @return this
     */
    JsonValue& operator=(const std::string& s) { Set(s); return *this; }

    /**
     * @brief Set this value to a string
     * @return this
     */
    JsonValue& operator=(const char* s) { Set(s); return *this; }

    // BOOL

    /**
     * @return Converts this to a bool
     */
    operator bool() const { return Get<bool>(); }

    /**
     * @brief Set this value to a bool
     * @return this
     */
    JsonValue& operator=(bool b) { Set(b); return *this; }

    // JSON OBJECT

    /**
     * @return Converts this to a JsonObject
     */
    operator JsonObject() const;

    /**
     * @brief Set this value to a JsonObject
     * @return this
     */
    JsonValue& operator=(const JsonObject& o);

    // JSON ARRAY

    /**
     * @return Converts this to a JsonArray
     */
    operator JsonArray() const;

    /**
     * @brief Set this value to a JsonArray
     * @return this
     */
    JsonValue& operator=(const JsonArray& a);

    /**
     * @brief Serialize this to valid JSON text.
     * @return This as a serialized JSON-formatted string.
     */
    std::string ToString() const { return GetValue().serialize(); }

protected:
    template<class T>
    T Get() const { return GetValue().get<T>(); }

    template<class T>
    void Set(const T& t) { GetValue() = picojson::value(t); }

    union {
        picojson::value v;
        std::reference_wrapper<picojson::value> ref;
    };

    bool isRef = false;
};

/**
 * @class Manages a picojson::object, works with JsonValue to provide implicit casting
 */
class JsonObject {

public:

    /**
     * @brief Default constructor
     */
    JsonObject() = default;

    /**
     * @brief Copy a picojson::object into this
     * @param obj: a picojson::object
     */
    JsonObject(picojson::object obj) : obj(std::move(obj)) {}

    /**
     * @brief Read/write access to a value in this JSON object from a given key.
     * You can add to the JsonObject this way.
     *
     * @param key: the key
     * @return read/write access to a JsonValue
     *
     * @example
     * @code
     * obj["my_number"] = 9.0;
     * obj["my_string"] = "hello";
     * obj["my_array"] = JsonArray();
     */
    JsonValue operator[](const std::string& key) { return JsonValue::FromReference(obj[key]); }

    /**
     * @brief Read only access to a value in this JSON object from a given key.
     * Requires existence of provided key. The returned value IS A COPY.
     * @throws std::runtime_error: if the key does not exist already
     * @param key: the key
     * @return a COPY of the value at the provided key
     *
     * @example
     * @code
     * double num = obj["my_number"];
     * std::string s = obj["my_string"];
     */
    JsonValue operator[](const std::string& key) const { return obj.at(key); }

    /**
     * @return The underlying picojson::object
     */
    picojson::object& GetObject() { return obj; }

    /**
     * @return The underlying picojson::object
     */
    const picojson::object& GetObject() const { return obj; }

    /**
     * @brief Serialize this to valid JSON text.
     * @return This as a serialized JSON-formatted string.
     */
    std::string ToString() const { return picojson::value(obj).serialize(); }

    /**
     * @brief Check if this JSON object contains a given key.
     * @param key: the key
     * @return whether or not key exists in this JsonObject
     */
    bool Contains(const std::string& key) const { return obj.find(key) != obj.end(); }

    /**
     * @return A vector of all the keys in this JsonObject
     */
    std::vector<std::string> GetKeys() const;

protected:
    picojson::object obj;
};

/**
 * @class Manages an underlying picojson::array. Works with JsonValue for implicit casting.
 */
class JsonArray {

public:

    /**
     * @brief Default constructor.
     */
    JsonArray() = default;

    /**
     * @brief Construct using a copy of a provided picojson::array
     * @param arr: a picojson::array
     */
    JsonArray(picojson::array arr) : arr(std::move(arr)) {}

    /**
     * @brief Construct using an initializer list
     * @param ls: the initializer list
     *
     * @example
     * @code
     * JsonArray arr = {9.0, "Hello", 1, JsonArray()};
     */
    JsonArray(const std::initializer_list<JsonValue> ls) { for (const auto& val : ls) Push(val); }

    /**
     * @brief Initialize a JsonArray to a given size.
     * @param size: the size of the array
     */
    explicit JsonArray(int size) : arr(size) {}

    /**
     * @return The underlying picojson::array
     */
    picojson::array& GetArray() { return arr; }

    /**
     * @return The underlying picojson::array
     */
    const picojson::array& GetArray() const { return arr; }

    /**
     * @brief Serialize this to valid JSON text.
     * @return This as a serialized JSON-formatted string.
     */
    std::string ToString() const { return picojson::value(arr).serialize(); }

    /**
     * @brief Read-write access to the entry at the given index.
     * You can modify an existing entry using this.
     * @param idx: the index
     * @return read/write access to a JsonValue
     *
     * @example
     * @code
     * JsonArray arr(3);
     * arr[0] = 2;
     * arr[1] = "Apple";
     * arr[2] = JsonObject();
     */
    JsonValue operator[](int idx) { return JsonValue::FromReference(arr[idx]); }

    /**
     * @brief Read-only access to the entry at the given index.
     * This entry must be within the bounds of the array.
     * This returns a COPY of the entry.
     * @throws std::runtime_error: if the index is not within the bounds of the array
     * @param idx: the index
     * @return a COPY of the entry at the given index.
     *
     * @example
     * @code
     * double num = arr[0];
     * std::string str = arr[1];
     * JsonObject obj = arr[2];
     */
    JsonValue operator[](int idx) const { return arr.at(idx); }

    /**
     * @brief Refer to operator[] const
     * @throws std::runtime_error: if the index is not within the bounds of the array
     * @param idx: the index
     * @return a COPY of the entry at the given index
     */
    JsonValue At(int idx) const { return arr.at(idx); }

    /**
     * @brief Push a JsonValue to the array (like std::vector::push_back())
     * @param val: the value to push
     *
     * @example
     * @code
     * arr.push_back(5.4);
     * arr.push_back("Car");
     */
    void Push(const JsonValue& val) { arr.push_back(val.GetValue()); }

    /**
     * @return The size of the array
     */
    int Size() const { return arr.size(); }

    /**
     * @brief Resize the array to the given size.
     * @param size: the new size
     */
    void Resize(int size) { arr.resize(size); }

protected:
    picojson::array arr;
};

class JsonSession : public JSONSession {
public:

    /**
     * @brief Initializes the server
     */
    JsonSession() = default;

    ~JsonSession() override = default;

    /**
     * @brief Receive a command from the web server
     * @param val: the command (in JSON format)
     */
    void receiveJSON(picojson::value& val) {
        JsonObject data = JsonValue(val);

        std::string cmd = data["command"];

        JsonObject returnValue;
        returnValue["id"] = data["id"];

        ReceiveCommand(cmd, data, returnValue);
        JsonValue retVal(returnValue);
        sendJSON(retVal.GetValue());
    }

    /**
     * @brief Handles specific commands from the web server
     * @param cmd: command
     * @param data: data (JSON)
     * @param returnValue: return value (JSON) which will be sent back to the server
     */
    virtual void ReceiveCommand(const std::string& cmd, JsonObject& data, JsonObject& returnValue) = 0;
};

inline JsonValue::~JsonValue() {
    if (!isRef) {
        v.~value();
    }
}

inline JsonValue::JsonValue(const JsonValue& other) {
    isRef = other.isRef;
    if (isRef) {
        ref = other.ref;
    } else {
        v = other.v;
    }
}

inline JsonValue& JsonValue::operator=(const JsonValue& other) {
    if (this != &other) {
        isRef = other.isRef;
        if (isRef) {
            ref = other.ref;
        } else {
            v = other.v;
        }
    }
    return *this;
}

inline JsonValue::JsonValue(JsonValue&& other) {
    isRef = other.isRef;
    if (isRef) {
        ref = other.ref;
    } else {
        v = std::move(other.v);
    }
}

inline JsonValue& JsonValue::operator=(JsonValue&& other) noexcept {
    isRef = other.isRef;
    if (isRef) {
        ref = other.ref;
    } else {
        v = std::move(other.v);
    }
    return *this;
}

inline JsonValue JsonValue::FromReference(picojson::value& v) {
    JsonValue val;
    val.ref = v;
    val.isRef = true;
    return val;
}

inline JsonValue& JsonValue::operator=(const JsonObject& o) {
    Set(o.GetObject());
    return *this;
}

inline JsonValue::operator JsonObject() const {
    return {Get<picojson::object>()};
}

inline JsonValue& JsonValue::operator=(const JsonArray& a) {
    Set(a.GetArray());
    return *this;
}

inline JsonValue::operator JsonArray() const {
    return {Get<picojson::array>()};
}

inline JsonValue::JsonValue(const JsonObject &o) : v(o.GetObject()) {}

inline JsonValue::JsonValue(const JsonArray &a) : v(a.GetArray()) {}

inline std::vector<std::string> JsonObject::GetKeys() const {
    std::vector<std::string> keys;
    for (const auto& kv : obj) {
        keys.push_back(kv.first);
    }
    return keys;
}

/**
 * @brief Operator overload for writing to a stream.
 */
inline std::ostream& operator<<(std::ostream& os, const JsonValue& val) {
    os << val.ToString();
    return os;
}

/**
 * @brief Operator overload for writing to a stream.
 */
inline std::ostream& operator<<(std::ostream& os, const JsonObject& obj) {
    os << obj.ToString();
    return os;
}

/**
 * @brief Operator overload for writing to a stream.
 */
inline std::ostream& operator<<(std::ostream& os, const JsonArray& array) {
    os << array.ToString();
    return os;
}

#endif //UTIL_JSON_H_