Commit 63de910d authored by Milo Yip's avatar Milo Yip

Merge pull request #57 from pah/cleanup/string-handling

Improved handling of (constant) strings
parents 80a1ec30 12c5805c
......@@ -1990,7 +1990,9 @@ INCLUDE_FILE_PATTERNS =
# recursively expanded use the := operator instead of the = operator.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
PREDEFINED =
PREDEFINED = \
RAPIDJSON_DOXYGEN_RUNNING \
RAPIDJSON_DISABLEIF_RETURN(cond,returntype)=returntype
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The
......
......@@ -313,6 +313,17 @@ Value o(kObjectType);
This is called move assignment operator in C++11. As RapidJSON supports C++03, it adopts move semantics using assignment operator, and all other modifying function like `AddMember()`, `PushBack()`.
### Move semantics and temporary values {#TemporaryValues}
Sometimes, it is convenient to construct a Value in place, before passing it to one of the "moving" functions, like `PushBack()` or `AddMember()`. As temporary objects can't be converted to proper Value references, the convenience function `Move()` is available:
~~~~~~~~~~cpp
Value a(kArrayType);
// a.PushBack(Value(42)); // will not compile
a.PushBack(Value().SetInt(42)); // fluent API
a.PushBack(Value(42).Move()); // same as above
~~~~~~~~~~
## Create String {#CreateString}
RapidJSON provide two strategies for storing string.
......@@ -339,15 +350,28 @@ In this example, we get the allocator from a `Document` instance. This is a comm
Besides, the above `SetString()` requires length. This can handle null characters within a string. There is another `SetString()` overloaded function without the length parameter. And it assumes the input is null-terminated and calls a `strlen()`-like function to obtain the length.
Finally, for literal string or string with safe life-cycle can use const-string version of `SetString()`, which lacks allocator parameter:
Finally, for literal string or string with safe life-cycle can use const-string version of `SetString()`, which lacks allocator parameter. For string literals (or constant character arrays), simply passing the literal as parameter is safe and efficient:
~~~~~~~~~~cpp
Value s;
s.SetString("rapidjson", 9); // faster, can contain null character
s.SetString("rapidjson"); // slower, assumes null-terminated
s.SetString("rapidjson"); // can contain null character, length derived at compile time
s = "rapidjson"; // shortcut, same as above
~~~~~~~~~~
For plain string pointers, the RapidJSON requires to mark a string as safe before using it without copying. This can be achieved by using the `StringRef` function:
~~~~~~~~~cpp
const char * cstr = getenv("USER");
size_t cstr_len = ...; // in case length is available
Value s;
// s.SetString(cstr); // will not compile
s.SetString(StringRef(cstr)); // ok, assume safe lifetime, null-terminated
s = StringRef(cstr); // shortcut, same as above
s.SetString(StringRef(cstr,cstr_len)); // faster, can contain null character
s = StringRef(cstr,cstr_len); // shortcut, same as above
~~~~~~~~~
## Modify Array {#ModifyArray}
Value with array type provides similar APIs as `std::vector`.
......@@ -357,7 +381,7 @@ Value with array type provides similar APIs as `std::vector`.
* `template <typename T> GenericValue& PushBack(T, Allocator&)`
* `Value& PopBack()`
Note that, `Reserve(...)` and `PushBack(...)` may allocate memory, therefore requires an allocator.
Note that, `Reserve(...)` and `PushBack(...)` may allocate memory for the array elements, therefore require an allocator.
Here is an example of `PushBack()`:
......@@ -372,14 +396,26 @@ for (int i = 5; i <= 10; i++)
a.PushBack("Lua", allocator).PushBack("Mio", allocator);
~~~~~~~~~~
Differs from STL, `PushBack()`/`PopBack()` returns the array reference itself. This is called fluent interface.
Differs from STL, `PushBack()`/`PopBack()` returns the array reference itself. This is called _fluent interface_.
If you want to add a non-constant string or a string without sufficient lifetime (see [Create String](#CreateString)) to the array, you need to create a string Value by using the copy-string API. To avoid the need for an intermediate variable, you can use a [temporary value](#TemporaryValues) in place:
~~~~~~~~~~cpp
// in-place Value parameter
contact.PushBack(Value("copy", document.GetAllocator()).Move(), // copy string
document.GetAllocator());
// explicit parameters
Value val("key", document.GetAllocator()); // copy string
contact.PushBack(val, document.GetAllocator());
~~~~~~~~~~
## Modify Object {#ModifyObject}
Object is a collection of key-value pairs. Each key must be a string value. The way to manipulating object is to add/remove members:
* `Value& AddMember(Value&, Value&, Allocator& allocator)`
* `Value& AddMember(const Ch*, Value&, Allocator&)`
* `template <typename T> Value& AddMember(const Ch*, T value, Allocator&)`
* `Value& AddMember(StringRefType, Value&, Allocator&)`
* `template <typename T> Value& AddMember(StringRefType, T value, Allocator&)`
* `bool RemoveMember(const Ch*)`
Here is an example.
......@@ -390,6 +426,22 @@ contact.AddMember("name", "Milo", document.GetAllocator());
contact.AddMember("married", true, document.GetAllocator());
~~~~~~~~~~
The `StringRefType` used as name parameter assumes the same interface as the `SetString` function for string values. These overloads are used to avoid the need for copying the `name` string, as constant key names are very common in JSON objects.
If you need to create a name from a non-constant string or a string without sufficient lifetime (see [Create String](#CreateString)), you need to create a string Value by using the copy-string API. To avoid the need for an intermediate variable, you can use a [temporary value](#TemporaryValues) in place:
~~~~~~~~~~cpp
// in-place Value parameter
contact.AddMember(Value("copy", document.GetAllocator()).Move(), // copy string
Value().Move(), // null value
document.GetAllocator());
// explicit parameters
Value key("key", document.GetAllocator()); // copy name string
Value val(42); // some value
contact.AddMember(key, val, document.GetAllocator());
~~~~~~~~~~
## Deep Copy Value {#DeepCopyValue}
If we really need to copy a DOM tree, we can use two APIs for deep copy: constructor with allocator, and `CopyFrom()`.
......
This diff is collapsed.
......@@ -24,11 +24,14 @@ struct SelectIf : SelectIfCond<Condition::Value,T1,T2> {};
template <bool Constify, typename T>
struct MaybeAddConst : SelectIfCond<Constify, const T, T> {};
template <typename T, typename U> struct IsSame { enum { Value = false }; };
template <typename T> struct IsSame<T,T> { enum { Value = true }; };
template <typename T, typename U> struct IsSame : FalseType {};
template <typename T> struct IsSame<T,T> : TrueType {};
template <typename T> struct IsConst { enum { Value = false }; };
template <typename T> struct IsConst<const T> { enum { Value = true }; };
template <typename T> struct IsConst : FalseType {};
template <typename T> struct IsConst<const T> : TrueType {};
template <typename T> struct IsPointer : FalseType {};
template <typename T> struct IsPointer<T*> : TrueType {};
template <typename CT, typename T>
struct IsMoreConst {
......@@ -64,6 +67,9 @@ template <typename T> struct RemoveSfinaeFptr<SfinaeResultTag&(*)(T)> { typedef
typename ::rapidjson::internal::EnableIf \
<RAPIDJSON_REMOVEFPTR_(cond)>::Type * = NULL
#define RAPIDJSON_DISABLEIF_RETURN(cond,returntype) \
typename ::rapidjson::internal::DisableIf<cond,returntype>::Type
} // namespace internal
} // namespace rapidjson
//@endcond
......
......@@ -23,6 +23,25 @@ TEST(Value, assignment_operator) {
y = x;
EXPECT_TRUE(x.IsNull()); // move semantic
EXPECT_EQ(1234, y.GetInt());
y = 5678;
EXPECT_TRUE(y.IsInt());
EXPECT_EQ(5678, y.GetInt());
x = "Hello";
EXPECT_TRUE(x.IsString());
EXPECT_STREQ(x.GetString(),"Hello");
y = StringRef(x.GetString(),x.GetStringLength());
EXPECT_TRUE(y.IsString());
EXPECT_EQ(y.GetString(),x.GetString());
EXPECT_EQ(y.GetStringLength(),x.GetStringLength());
static char mstr[] = "mutable";
// y = mstr; // should not compile
y = StringRef(mstr);
EXPECT_TRUE(y.IsString());
EXPECT_EQ(y.GetString(),mstr);
}
template <typename Value>
......@@ -350,8 +369,8 @@ TEST(Value, Double) {
}
TEST(Value, String) {
// Constructor with const string
Value x("Hello", 5);
// Construction with const string
Value x("Hello", 5); // literal
EXPECT_EQ(kStringType, x.GetType());
EXPECT_TRUE(x.IsString());
EXPECT_STREQ("Hello", x.GetString());
......@@ -365,9 +384,41 @@ TEST(Value, String) {
EXPECT_FALSE(x.IsObject());
EXPECT_FALSE(x.IsArray());
static const char cstr[] = "World"; // const array
Value(cstr).Swap(x);
EXPECT_TRUE(x.IsString());
EXPECT_EQ(x.GetString(), cstr);
EXPECT_EQ(x.GetStringLength(), sizeof(cstr)-1);
static char mstr[] = "Howdy"; // non-const array
// Value(mstr).Swap(x); // should not compile
Value(StringRef(mstr)).Swap(x);
EXPECT_TRUE(x.IsString());
EXPECT_EQ(x.GetString(), mstr);
EXPECT_EQ(x.GetStringLength(), sizeof(mstr)-1);
strncpy(mstr,"Hello", sizeof(mstr));
EXPECT_STREQ(x.GetString(), "Hello");
const char* pstr = cstr;
//Value(pstr).Swap(x); // should not compile
Value(StringRef(pstr)).Swap(x);
EXPECT_TRUE(x.IsString());
EXPECT_EQ(x.GetString(), cstr);
EXPECT_EQ(x.GetStringLength(), sizeof(cstr)-1);
char* mpstr = mstr;
Value(StringRef(mpstr,sizeof(mstr)-1)).Swap(x);
EXPECT_TRUE(x.IsString());
EXPECT_EQ(x.GetString(), mstr);
EXPECT_EQ(x.GetStringLength(), 5u);
EXPECT_STREQ(x.GetString(), "Hello");
// Constructor with copy string
MemoryPoolAllocator<> allocator;
Value c(x.GetString(), x.GetStringLength(), allocator);
EXPECT_NE(x.GetString(), c.GetString());
EXPECT_EQ(x.GetStringLength(), c.GetStringLength());
EXPECT_STREQ(x.GetString(), c.GetString());
//x.SetString("World");
x.SetString("World", 5);
EXPECT_STREQ("Hello", c.GetString());
......@@ -381,11 +432,31 @@ TEST(Value, String) {
// SetConsttring()
Value z;
//z.SetString("Hello");
z.SetString("Hello");
EXPECT_TRUE(x.IsString());
z.SetString("Hello", 5);
EXPECT_STREQ("Hello", z.GetString());
EXPECT_STREQ("Hello", z.GetString());
EXPECT_EQ(5u, z.GetStringLength());
z.SetString("Hello");
EXPECT_TRUE(z.IsString());
EXPECT_STREQ("Hello", z.GetString());
//z.SetString(mstr); // should not compile
//z.SetString(pstr); // should not compile
z.SetString(StringRef(mstr));
EXPECT_TRUE(z.IsString());
EXPECT_STREQ(z.GetString(), mstr);
z.SetString(cstr);
EXPECT_TRUE(z.IsString());
EXPECT_EQ(cstr, z.GetString());
z = cstr;
EXPECT_TRUE(z.IsString());
EXPECT_EQ(cstr, z.GetString());
// SetString()
char s[] = "World";
Value w;
......@@ -424,11 +495,13 @@ TEST(Value, Array) {
x.PushBack(v, allocator);
v.SetInt(123);
x.PushBack(v, allocator);
//x.PushBack((const char*)"foo", allocator); // should not compile
x.PushBack("foo", allocator);
EXPECT_FALSE(x.Empty());
EXPECT_EQ(4u, x.Size());
EXPECT_EQ(5u, x.Size());
EXPECT_FALSE(y.Empty());
EXPECT_EQ(4u, y.Size());
EXPECT_EQ(5u, y.Size());
EXPECT_TRUE(x[SizeType(0)].IsNull());
EXPECT_TRUE(x[1u].IsTrue());
EXPECT_TRUE(x[2u].IsFalse());
......@@ -439,6 +512,8 @@ TEST(Value, Array) {
EXPECT_TRUE(y[2u].IsFalse());
EXPECT_TRUE(y[3u].IsInt());
EXPECT_EQ(123, y[3u].GetInt());
EXPECT_TRUE(y[4u].IsString());
EXPECT_STREQ("foo", y[4u].GetString());
// iterator
Value::ValueIterator itr = x.Begin();
......@@ -454,6 +529,10 @@ TEST(Value, Array) {
EXPECT_TRUE(itr != x.End());
EXPECT_TRUE(itr->IsInt());
EXPECT_EQ(123, itr->GetInt());
++itr;
EXPECT_TRUE(itr != x.End());
EXPECT_TRUE(itr->IsString());
EXPECT_STREQ("foo", itr->GetString());
// const iterator
Value::ConstValueIterator citr = y.Begin();
......@@ -469,13 +548,18 @@ TEST(Value, Array) {
EXPECT_TRUE(citr != y.End());
EXPECT_TRUE(citr->IsInt());
EXPECT_EQ(123, citr->GetInt());
++citr;
EXPECT_TRUE(citr != y.End());
EXPECT_TRUE(citr->IsString());
EXPECT_STREQ("foo", citr->GetString());
// PopBack()
x.PopBack();
EXPECT_EQ(3u, x.Size());
EXPECT_EQ(4u, x.Size());
EXPECT_TRUE(y[SizeType(0)].IsNull());
EXPECT_TRUE(y[1].IsTrue());
EXPECT_TRUE(y[2].IsFalse());
EXPECT_TRUE(y[1u].IsTrue());
EXPECT_TRUE(y[2u].IsFalse());
EXPECT_TRUE(y[3u].IsInt());
// Clear()
x.Clear();
......@@ -502,16 +586,34 @@ TEST(Value, Object) {
EXPECT_TRUE(y.IsObject());
// AddMember()
Value name("A", 1);
Value value("Apple", 5);
x.AddMember(name, value, allocator);
//name.SetString("B");
name.SetString("B", 1);
//value.SetString("Banana");
value.SetString("Banana", 6);
x.AddMember(name, value, allocator);
x.AddMember("A", "Apple", allocator);
Value value("Banana", 6);
x.AddMember("B", "Banana", allocator);
// AddMember<T>(StringRefType, T, Allocator)
{
Value o(kObjectType);
o.AddMember("true", true, allocator);
o.AddMember("false", false, allocator);
o.AddMember("int", -1, allocator);
o.AddMember("uint", 1u, allocator);
o.AddMember("int64", INT64_C(-4294967296), allocator);
o.AddMember("uint64", UINT64_C(4294967296), allocator);
o.AddMember("double", 3.14, allocator);
o.AddMember("string", "Jelly", allocator);
EXPECT_TRUE(o["true"].GetBool());
EXPECT_FALSE(o["false"].GetBool());
EXPECT_EQ(-1, o["int"].GetInt());
EXPECT_EQ(1u, o["uint"].GetUint());
EXPECT_EQ(INT64_C(-4294967296), o["int64"].GetInt64());
EXPECT_EQ(UINT64_C(4294967296), o["uint64"].GetUint64());
EXPECT_STREQ("Jelly",o["string"].GetString());
}
// Tests a member with null character
Value name;
const Value C0D("C\0D", 3);
name.SetString(C0D.GetString(), 3);
value.SetString("CherryD", 7);
......@@ -523,7 +625,7 @@ TEST(Value, Object) {
EXPECT_TRUE(y.HasMember("A"));
EXPECT_TRUE(y.HasMember("B"));
name.SetString("C\0D", 3);
name.SetString("C\0D");
EXPECT_TRUE(x.HasMember(name));
EXPECT_TRUE(y.HasMember(name));
......@@ -617,6 +719,7 @@ TEST(Value, BigNestedObject) {
char name1[10];
sprintf(name1, "%d", i);
// Value name(name1); // should not compile
Value name(name1, (SizeType)strlen(name1), allocator);
Value object(kObjectType);
......@@ -629,6 +732,7 @@ TEST(Value, BigNestedObject) {
object.AddMember(name, number, allocator);
}
// x.AddMember(name1, object, allocator); // should not compile
x.AddMember(name, object, allocator);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment