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 = ...@@ -1990,7 +1990,9 @@ INCLUDE_FILE_PATTERNS =
# recursively expanded use the := operator instead of the = operator. # recursively expanded use the := operator instead of the = operator.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. # 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 # 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 # tag can be used to specify a list of macro names that should be expanded. The
......
...@@ -313,6 +313,17 @@ Value o(kObjectType); ...@@ -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()`. 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} ## Create String {#CreateString}
RapidJSON provide two strategies for storing string. 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 ...@@ -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. 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 ~~~~~~~~~~cpp
Value s; Value s;
s.SetString("rapidjson", 9); // faster, can contain null character s.SetString("rapidjson"); // can contain null character, length derived at compile time
s.SetString("rapidjson"); // slower, assumes null-terminated
s = "rapidjson"; // shortcut, same as above 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} ## Modify Array {#ModifyArray}
Value with array type provides similar APIs as `std::vector`. Value with array type provides similar APIs as `std::vector`.
...@@ -357,7 +381,7 @@ 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&)` * `template <typename T> GenericValue& PushBack(T, Allocator&)`
* `Value& PopBack()` * `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()`: Here is an example of `PushBack()`:
...@@ -372,14 +396,26 @@ for (int i = 5; i <= 10; i++) ...@@ -372,14 +396,26 @@ for (int i = 5; i <= 10; i++)
a.PushBack("Lua", allocator).PushBack("Mio", allocator); 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} ## 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: 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(Value&, Value&, Allocator& allocator)`
* `Value& AddMember(const Ch*, Value&, Allocator&)` * `Value& AddMember(StringRefType, Value&, Allocator&)`
* `template <typename T> Value& AddMember(const Ch*, T value, Allocator&)` * `template <typename T> Value& AddMember(StringRefType, T value, Allocator&)`
* `bool RemoveMember(const Ch*)` * `bool RemoveMember(const Ch*)`
Here is an example. Here is an example.
...@@ -390,6 +426,22 @@ contact.AddMember("name", "Milo", document.GetAllocator()); ...@@ -390,6 +426,22 @@ contact.AddMember("name", "Milo", document.GetAllocator());
contact.AddMember("married", true, 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} ## 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()`. 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> {}; ...@@ -24,11 +24,14 @@ struct SelectIf : SelectIfCond<Condition::Value,T1,T2> {};
template <bool Constify, typename T> template <bool Constify, typename T>
struct MaybeAddConst : SelectIfCond<Constify, const T, T> {}; struct MaybeAddConst : SelectIfCond<Constify, const T, T> {};
template <typename T, typename U> struct IsSame { enum { Value = false }; }; template <typename T, typename U> struct IsSame : FalseType {};
template <typename T> struct IsSame<T,T> { enum { Value = true }; }; template <typename T> struct IsSame<T,T> : TrueType {};
template <typename T> struct IsConst { enum { Value = false }; }; template <typename T> struct IsConst : FalseType {};
template <typename T> struct IsConst<const T> { enum { Value = true }; }; 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> template <typename CT, typename T>
struct IsMoreConst { struct IsMoreConst {
...@@ -64,6 +67,9 @@ template <typename T> struct RemoveSfinaeFptr<SfinaeResultTag&(*)(T)> { typedef ...@@ -64,6 +67,9 @@ template <typename T> struct RemoveSfinaeFptr<SfinaeResultTag&(*)(T)> { typedef
typename ::rapidjson::internal::EnableIf \ typename ::rapidjson::internal::EnableIf \
<RAPIDJSON_REMOVEFPTR_(cond)>::Type * = NULL <RAPIDJSON_REMOVEFPTR_(cond)>::Type * = NULL
#define RAPIDJSON_DISABLEIF_RETURN(cond,returntype) \
typename ::rapidjson::internal::DisableIf<cond,returntype>::Type
} // namespace internal } // namespace internal
} // namespace rapidjson } // namespace rapidjson
//@endcond //@endcond
......
...@@ -23,6 +23,25 @@ TEST(Value, assignment_operator) { ...@@ -23,6 +23,25 @@ TEST(Value, assignment_operator) {
y = x; y = x;
EXPECT_TRUE(x.IsNull()); // move semantic EXPECT_TRUE(x.IsNull()); // move semantic
EXPECT_EQ(1234, y.GetInt()); 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> template <typename Value>
...@@ -350,8 +369,8 @@ TEST(Value, Double) { ...@@ -350,8 +369,8 @@ TEST(Value, Double) {
} }
TEST(Value, String) { TEST(Value, String) {
// Constructor with const string // Construction with const string
Value x("Hello", 5); Value x("Hello", 5); // literal
EXPECT_EQ(kStringType, x.GetType()); EXPECT_EQ(kStringType, x.GetType());
EXPECT_TRUE(x.IsString()); EXPECT_TRUE(x.IsString());
EXPECT_STREQ("Hello", x.GetString()); EXPECT_STREQ("Hello", x.GetString());
...@@ -365,9 +384,41 @@ TEST(Value, String) { ...@@ -365,9 +384,41 @@ TEST(Value, String) {
EXPECT_FALSE(x.IsObject()); EXPECT_FALSE(x.IsObject());
EXPECT_FALSE(x.IsArray()); 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 // Constructor with copy string
MemoryPoolAllocator<> allocator; MemoryPoolAllocator<> allocator;
Value c(x.GetString(), x.GetStringLength(), 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");
x.SetString("World", 5); x.SetString("World", 5);
EXPECT_STREQ("Hello", c.GetString()); EXPECT_STREQ("Hello", c.GetString());
...@@ -381,11 +432,31 @@ TEST(Value, String) { ...@@ -381,11 +432,31 @@ TEST(Value, String) {
// SetConsttring() // SetConsttring()
Value z; Value z;
//z.SetString("Hello"); z.SetString("Hello");
EXPECT_TRUE(x.IsString());
z.SetString("Hello", 5); z.SetString("Hello", 5);
EXPECT_STREQ("Hello", z.GetString()); EXPECT_STREQ("Hello", z.GetString());
EXPECT_STREQ("Hello", z.GetString());
EXPECT_EQ(5u, z.GetStringLength()); 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() // SetString()
char s[] = "World"; char s[] = "World";
Value w; Value w;
...@@ -424,11 +495,13 @@ TEST(Value, Array) { ...@@ -424,11 +495,13 @@ TEST(Value, Array) {
x.PushBack(v, allocator); x.PushBack(v, allocator);
v.SetInt(123); v.SetInt(123);
x.PushBack(v, allocator); x.PushBack(v, allocator);
//x.PushBack((const char*)"foo", allocator); // should not compile
x.PushBack("foo", allocator);
EXPECT_FALSE(x.Empty()); EXPECT_FALSE(x.Empty());
EXPECT_EQ(4u, x.Size()); EXPECT_EQ(5u, x.Size());
EXPECT_FALSE(y.Empty()); EXPECT_FALSE(y.Empty());
EXPECT_EQ(4u, y.Size()); EXPECT_EQ(5u, y.Size());
EXPECT_TRUE(x[SizeType(0)].IsNull()); EXPECT_TRUE(x[SizeType(0)].IsNull());
EXPECT_TRUE(x[1u].IsTrue()); EXPECT_TRUE(x[1u].IsTrue());
EXPECT_TRUE(x[2u].IsFalse()); EXPECT_TRUE(x[2u].IsFalse());
...@@ -439,6 +512,8 @@ TEST(Value, Array) { ...@@ -439,6 +512,8 @@ TEST(Value, Array) {
EXPECT_TRUE(y[2u].IsFalse()); EXPECT_TRUE(y[2u].IsFalse());
EXPECT_TRUE(y[3u].IsInt()); EXPECT_TRUE(y[3u].IsInt());
EXPECT_EQ(123, y[3u].GetInt()); EXPECT_EQ(123, y[3u].GetInt());
EXPECT_TRUE(y[4u].IsString());
EXPECT_STREQ("foo", y[4u].GetString());
// iterator // iterator
Value::ValueIterator itr = x.Begin(); Value::ValueIterator itr = x.Begin();
...@@ -454,6 +529,10 @@ TEST(Value, Array) { ...@@ -454,6 +529,10 @@ TEST(Value, Array) {
EXPECT_TRUE(itr != x.End()); EXPECT_TRUE(itr != x.End());
EXPECT_TRUE(itr->IsInt()); EXPECT_TRUE(itr->IsInt());
EXPECT_EQ(123, itr->GetInt()); EXPECT_EQ(123, itr->GetInt());
++itr;
EXPECT_TRUE(itr != x.End());
EXPECT_TRUE(itr->IsString());
EXPECT_STREQ("foo", itr->GetString());
// const iterator // const iterator
Value::ConstValueIterator citr = y.Begin(); Value::ConstValueIterator citr = y.Begin();
...@@ -469,13 +548,18 @@ TEST(Value, Array) { ...@@ -469,13 +548,18 @@ TEST(Value, Array) {
EXPECT_TRUE(citr != y.End()); EXPECT_TRUE(citr != y.End());
EXPECT_TRUE(citr->IsInt()); EXPECT_TRUE(citr->IsInt());
EXPECT_EQ(123, citr->GetInt()); EXPECT_EQ(123, citr->GetInt());
++citr;
EXPECT_TRUE(citr != y.End());
EXPECT_TRUE(citr->IsString());
EXPECT_STREQ("foo", citr->GetString());
// PopBack() // PopBack()
x.PopBack(); x.PopBack();
EXPECT_EQ(3u, x.Size()); EXPECT_EQ(4u, x.Size());
EXPECT_TRUE(y[SizeType(0)].IsNull()); EXPECT_TRUE(y[SizeType(0)].IsNull());
EXPECT_TRUE(y[1].IsTrue()); EXPECT_TRUE(y[1u].IsTrue());
EXPECT_TRUE(y[2].IsFalse()); EXPECT_TRUE(y[2u].IsFalse());
EXPECT_TRUE(y[3u].IsInt());
// Clear() // Clear()
x.Clear(); x.Clear();
...@@ -502,16 +586,34 @@ TEST(Value, Object) { ...@@ -502,16 +586,34 @@ TEST(Value, Object) {
EXPECT_TRUE(y.IsObject()); EXPECT_TRUE(y.IsObject());
// AddMember() // AddMember()
Value name("A", 1); x.AddMember("A", "Apple", allocator);
Value value("Apple", 5);
x.AddMember(name, value, allocator); Value value("Banana", 6);
//name.SetString("B"); x.AddMember("B", "Banana", allocator);
name.SetString("B", 1);
//value.SetString("Banana"); // AddMember<T>(StringRefType, T, Allocator)
value.SetString("Banana", 6); {
x.AddMember(name, value, 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 // Tests a member with null character
Value name;
const Value C0D("C\0D", 3); const Value C0D("C\0D", 3);
name.SetString(C0D.GetString(), 3); name.SetString(C0D.GetString(), 3);
value.SetString("CherryD", 7); value.SetString("CherryD", 7);
...@@ -523,7 +625,7 @@ TEST(Value, Object) { ...@@ -523,7 +625,7 @@ TEST(Value, Object) {
EXPECT_TRUE(y.HasMember("A")); EXPECT_TRUE(y.HasMember("A"));
EXPECT_TRUE(y.HasMember("B")); EXPECT_TRUE(y.HasMember("B"));
name.SetString("C\0D", 3); name.SetString("C\0D");
EXPECT_TRUE(x.HasMember(name)); EXPECT_TRUE(x.HasMember(name));
EXPECT_TRUE(y.HasMember(name)); EXPECT_TRUE(y.HasMember(name));
...@@ -617,6 +719,7 @@ TEST(Value, BigNestedObject) { ...@@ -617,6 +719,7 @@ TEST(Value, BigNestedObject) {
char name1[10]; char name1[10];
sprintf(name1, "%d", i); sprintf(name1, "%d", i);
// Value name(name1); // should not compile
Value name(name1, (SizeType)strlen(name1), allocator); Value name(name1, (SizeType)strlen(name1), allocator);
Value object(kObjectType); Value object(kObjectType);
...@@ -629,6 +732,7 @@ TEST(Value, BigNestedObject) { ...@@ -629,6 +732,7 @@ TEST(Value, BigNestedObject) {
object.AddMember(name, number, allocator); object.AddMember(name, number, allocator);
} }
// x.AddMember(name1, object, allocator); // should not compile
x.AddMember(name, object, allocator); 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