JString::Proxy offers several implicit conversion operators (ZString, MString, std::string, double, bool) so a single $[...]-style access can be used wherever any of those types is expected. That flexibility made "MString << Proxy" genuinely ambiguous: operator ZString() and operator MString() (via its own implicit upcast to ZString) both satisfy MString::operator<<(ZString), and operator double()/operator bool() both satisfy MString::operator<<(int) via a standard numeric conversion, so the compiler had no basis to prefer one conversion path over another. Fixed the same way Proxy already solves this exact problem for std::cout << proxy - a dedicated friend operator<<(MString&, const Proxy&) that needs no conversion at all, so it's always the preferred candidate. Verified by actually running testing/jstring_test.cpp (not just compiling it) - the line that used to fail to compile now correctly prints "this is a test: this is a comment". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
220 lines
6.6 KiB
C++
220 lines
6.6 KiB
C++
#ifndef __JString_h__
|
|
#define __JString_h__
|
|
|
|
#include "MString.h"
|
|
#include <iostream>
|
|
#include <sstream>
|
|
|
|
namespace coreutils {
|
|
|
|
///
|
|
/// Use the JString object when you need a JSON interface to C++. JString uses
|
|
/// an MString to store a JSON string representing the object(s) contained.
|
|
/// The [] operator is overriden from the ZString and provides a method to
|
|
/// access the objects elements using a named path as the index to the items.
|
|
///
|
|
/// JString default constructor will create an empty JString object pointing
|
|
/// to an empty object ("{}").
|
|
///
|
|
/// Use a constructor or assignment operator to initialize a JString to an
|
|
/// initial JSON string.
|
|
///
|
|
/// Several constructors are available including a handle to a std::FILE
|
|
/// object to initialize the JSON string. On destruction the file is saved back.
|
|
///
|
|
/// The [] operator can be used to access elements within the JSON string.
|
|
/// Assigning a value dispatches on the C++ type of the right-hand side, so the
|
|
/// resulting JSON is correctly typed rather than always being a quoted string:
|
|
///
|
|
/// jstring["name"] = "value"; // JSON string, escaped correctly
|
|
/// jstring["count"] = 42; // JSON number (integer)
|
|
/// jstring["price"] = 19.95; // JSON number
|
|
/// jstring["active"] = true; // JSON boolean
|
|
/// jstring["notes"].setNull(); // JSON null
|
|
///
|
|
/// std::cout << jstring["name"];
|
|
///
|
|
/// Typed read access is available directly on the path (no need to go through
|
|
/// operator[] first):
|
|
///
|
|
/// std::string name = jstring.getString("name");
|
|
/// double price = jstring.getNumber("price");
|
|
/// bool active = jstring.getBool("active");
|
|
/// bool missing = !jstring.exists("notfound");
|
|
/// JSONType t = jstring.getType("count"); // JSONType::NUMBER
|
|
///
|
|
|
|
enum class JSONType {
|
|
STRING, NUMBER, BOOLEAN, JNULL, ARRAY, OBJECT, NOT_FOUND
|
|
};
|
|
|
|
class JString : virtual public MString {
|
|
|
|
public:
|
|
|
|
JString();
|
|
|
|
void set(ZString &path, ZString &value);
|
|
|
|
///
|
|
/// Use the find method to look through an object for member specified by
|
|
/// the key in parameter 1. The cursor must be pointing to the { character
|
|
/// that begins the object's list. Returns the raw (still-escaped, for
|
|
/// strings) span of the value - use getString()/getNumber()/getBool()/
|
|
/// isNull() for decoded, typed access instead.
|
|
///
|
|
|
|
ZString find(ZString path);
|
|
|
|
bool locate(ZString path);
|
|
bool notfirst;
|
|
ZString path;
|
|
|
|
///
|
|
/// Type-aware access. All of these call locate() internally, so path uses
|
|
/// the same dotted/bracketed syntax as find()/set() (e.g. "a.b[2].c").
|
|
///
|
|
|
|
JSONType getType(ZString path);
|
|
bool exists(ZString path);
|
|
bool isNull(ZString path);
|
|
|
|
std::string getString(ZString path); ///< Escape-decoded string content.
|
|
double getNumber(ZString path);
|
|
long getInt(ZString path);
|
|
bool getBool(ZString path);
|
|
|
|
void setString(ZString path, std::string value); ///< Escapes value automatically.
|
|
void setNumber(ZString path, double value);
|
|
void setInt(ZString path, long value);
|
|
void setBool(ZString path, bool value);
|
|
void setNull(ZString path);
|
|
|
|
///
|
|
/// JSON string escape/unescape helpers (exposed as static utilities since
|
|
/// they operate on plain text, not on this object's own JSON document).
|
|
///
|
|
|
|
static std::string jsonEscape(ZString value);
|
|
static std::string jsonUnescape(ZString raw);
|
|
|
|
class Proxy {
|
|
|
|
public:
|
|
Proxy(JString &jstring, ZString key) : jstring(jstring), key(key) {}
|
|
|
|
friend std::ostream &operator<<(std::ostream &os, const Proxy &proxy);
|
|
|
|
///
|
|
/// Proxy offers several implicit conversion operators below (to
|
|
/// ZString, MString, std::string, double, bool) so a single $[...]-
|
|
/// style access can be used wherever any of those types is expected.
|
|
/// That flexibility makes "MString << Proxy" genuinely ambiguous on
|
|
/// its own - operator ZString() and operator MString() (via its own
|
|
/// implicit upcast to ZString) both satisfy MString::operator<<(ZString),
|
|
/// and operator double()/operator bool() both satisfy
|
|
/// MString::operator<<(int) via a standard numeric conversion - so
|
|
/// the compiler has no way to prefer one path over another. This
|
|
/// exact-match overload (mirroring the ostream one above, which
|
|
/// solves the same problem for std::cout << proxy) resolves it by
|
|
/// giving MString a candidate that needs no conversion at all.
|
|
///
|
|
|
|
friend MString &operator<<(MString &ms, const Proxy &proxy);
|
|
|
|
operator coreutils::ZString () {
|
|
return jstring.find(key);
|
|
}
|
|
|
|
operator coreutils::MString () {
|
|
return jstring.find(key);
|
|
}
|
|
|
|
operator std::string () {
|
|
return jstring.getString(key);
|
|
}
|
|
|
|
operator double () {
|
|
return jstring.getNumber(key);
|
|
}
|
|
|
|
operator bool () {
|
|
return jstring.getBool(key);
|
|
}
|
|
|
|
Proxy& operator=(coreutils::MString value) {
|
|
jstring.setString(key, value.str());
|
|
jstring.changed(key, value);
|
|
return *this;
|
|
}
|
|
|
|
Proxy& operator=(const char *value) {
|
|
jstring.setString(key, std::string(value));
|
|
jstring.changed(key, ZString(value));
|
|
return *this;
|
|
}
|
|
|
|
Proxy& operator=(double value) {
|
|
jstring.setNumber(key, value);
|
|
return *this;
|
|
}
|
|
|
|
Proxy& operator=(int value) {
|
|
jstring.setInt(key, value);
|
|
return *this;
|
|
}
|
|
|
|
Proxy& operator=(long value) {
|
|
jstring.setInt(key, value);
|
|
return *this;
|
|
}
|
|
|
|
Proxy& operator=(bool value) {
|
|
jstring.setBool(key, value);
|
|
return *this;
|
|
}
|
|
|
|
void setNull() {
|
|
jstring.setNull(key);
|
|
}
|
|
|
|
private:
|
|
JString &jstring;
|
|
ZString key;
|
|
};
|
|
|
|
Proxy operator[](coreutils::ZString key) {
|
|
return Proxy(*this, key);
|
|
}
|
|
|
|
JString & operator=(coreutils:: ZString value);
|
|
|
|
MString pretty();
|
|
|
|
virtual void changed(ZString key, ZString value);
|
|
|
|
private:
|
|
void removeWhitespace();
|
|
|
|
///
|
|
/// Structural helpers used internally by locate()/set() to correctly skip
|
|
/// past a value of any JSON type without being confused by JSON-structural
|
|
/// characters (quotes, braces, brackets) appearing inside string literals.
|
|
///
|
|
|
|
ZString findRelative(ZString path);
|
|
JSONType typeAtCursor();
|
|
void skipValue();
|
|
void skipString();
|
|
void skipContainer();
|
|
void skipNumber();
|
|
ZString captureString();
|
|
ZString captureContainer();
|
|
bool isLiteralValue(ZString value);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
#endif
|