diff --git a/doc/diagram/.gitignore b/doc/diagram/.gitignore new file mode 100644 index 0000000..a136337 --- /dev/null +++ b/doc/diagram/.gitignore @@ -0,0 +1 @@ +*.pdf diff --git a/doc/diagram/makefile b/doc/diagram/makefile index 4699438..3483977 100644 --- a/doc/diagram/makefile +++ b/doc/diagram/makefile @@ -5,4 +5,4 @@ dot $< -Tpng -o $@ DOTFILES = $(basename $(wildcard *.dot)) -all: $(addsuffix .png, $(DOTFILES)) #$(addsuffix .pdf, $(DOTFILES)) +all: $(addsuffix .png, $(DOTFILES)) $(addsuffix .pdf, $(DOTFILES)) diff --git a/doc/diagram/tutorial.dot b/doc/diagram/tutorial.dot new file mode 100644 index 0000000..b4f5d5f --- /dev/null +++ b/doc/diagram/tutorial.dot @@ -0,0 +1,58 @@ +digraph { + compound=true + fontname="Inconsolata, Consolas" + fontsize=10 + margin="0,0" + ranksep=0.2 + penwidth=0.5 + + node [fontname="Inconsolata, Consolas", fontsize=10, penwidth=0.5] + edge [fontname="Inconsolata, Consolas", fontsize=10] + + subgraph cluster1 { + margin="10,10" + labeljust="left" + label = "Document" + style=filled + fillcolor=gray95 + node [shape=Mrecord, style=filled, colorscheme=spectral7] + + root [label="{object|}", fillcolor=3] + + { + hello [label="{string|\"hello\"}", fillcolor=5] + t [label="{string|\"t\"}", fillcolor=5] + f [label="{string|\"f\"}", fillcolor=5] + n [label="{string|\"n\"}", fillcolor=5] + i [label="{string|\"i\"}", fillcolor=5] + pi [label="{string|\"pi\"}", fillcolor=5] + a [label="{string|\"a\"}", fillcolor=5] + + world [label="{string|\"world\"}", fillcolor=5] + true [label="{true|}", fillcolor=7] + false [label="{false|}", fillcolor=2] + null [label="{null|}", fillcolor=1] + i1 [label="{number|123}", fillcolor=6] + pi1 [label="{number|3.1416}", fillcolor=6] + array [label="{array|size=4}", fillcolor=4] + + a1 [label="{number|1}", fillcolor=6] + a2 [label="{number|2}", fillcolor=6] + a3 [label="{number|3}", fillcolor=6] + a4 [label="{number|4}", fillcolor=6] + } + + edge [arrowhead=vee] + root -> { hello, t, f, n, i, pi, a } + array -> { a1, a2, a3, a4} + + edge [arrowhead=none] + hello -> world + t -> true + f -> false + n -> null + i -> i1 + pi -> pi1 + a -> array + } +} \ No newline at end of file diff --git a/doc/diagram/tutorial.png b/doc/diagram/tutorial.png new file mode 100644 index 0000000..77c3a6d Binary files /dev/null and b/doc/diagram/tutorial.png differ diff --git a/doc/tutorial.md b/doc/tutorial.md index 02d61cf..2af1758 100644 --- a/doc/tutorial.md +++ b/doc/tutorial.md @@ -1,10 +1,10 @@ # RapidJSON Tutorial -This tutorial introduces Document Object Model(DOM) API of RapidJSON. +This tutorial introduces the basics of the Document Object Model(DOM) API. -As shown in [Usage at a glance](../readme.md#usage-at-a-glance), a JSON text can be parsed into DOM, and then be quried and modfied easily, and finally convert back to JSON text. +As shown in [Usage at a glance](../readme.md#usage-at-a-glance), a JSON text can be parsed into DOM, and then the DOM can be queried and modfied easily, and finally be converted back to JSON text. -## Value +## Value & Document Each JSON value is stored in a type called `Value`. A `Document`, representing the DOM, contains the root of `Value`. @@ -36,9 +36,11 @@ Document document; document.Parse(json); ``` -The JSON text is now parsed into `document` as a DOM tree. +The JSON text is now parsed into `document` as a DOM tree: -The root of a conforming JSON should be either an object or an array. In this case, the root is an object with 7 members. +![tutorial](diagram/tutorial.png?raw=true) + +The root of a conforming JSON should be either an object or an array. In this case, the root is an object. ```cpp assert(document.IsObject()); ``` @@ -50,17 +52,29 @@ assert(document["hello"].IsString()); printf("hello = %s\n", document["hello"].GetString()); ``` +``` +world +``` + JSON true/false values are represented as `bool`. ```cpp assert(document["t"].IsBool()); printf("t = %s\n", document["t"].GetBool() ? "true" : "false"); ``` +``` +true +``` + JSON null can be queryed by `IsNull()`. ```cpp printf("n = %s\n", document["n"].IsNull() ? "null" : "?"); ``` +``` +null +``` + JSON number type represents all numeric values. However, C++ needs more specific type for manipulation. ```cpp @@ -76,7 +90,12 @@ assert(document["pi"].IsDouble()); printf("pi = %g\n", document["pi"].GetDouble()); ``` -JSON array contains a number of elements +``` +i = 123 +pi = 3.1416 +``` + +JSON array contains a number of elements. ```cpp // Using a reference for consecutive access is handy and faster. const Value& a = document["a"]; @@ -85,16 +104,132 @@ for (SizeType i = 0; i < a.Size(); i++) // Uses SizeType instead of size_t printf("a[%d] = %d\n", i, a[i].GetInt()); ``` -Note that, RapidJSON do not automatically converting between JSON types. if a value is a string, it is invalid to call `GetInt()`. In debug mode it will assert. In release mode, the behavior is undefined. +``` +a[0] = 1 +a[1] = 2 +a[2] = 3 +a[3] = 4 +``` + +Note that, RapidJSON does not automatically convert values between JSON types. If a value is a string, it is invalid to call `GetInt()`, for example. In debug mode it will fail an assertion. In release mode, the behavior is undefined. + +In the following, details about querying individual types are discussed. + +### Querying Array + +By default, `SizeType` is typedef of `unsigned`. In most systems, array is limited to store up to 2^32-1 elements. + +You may access the elements in array by integer literal, for example, `a[1]`, `a[2]`. However, `a[0]` will generate a compiler error. It is because two overloaded operators `operator[](SizeType)` and `operator[](const char*)` is avaliable, and C++ can treat `0` as a null pointer. Workarounds: +* `a[SizeType(0)]` +* `a[0u]` + +Array is similar to `std::vector`, instead of using indices, you may also use iterator to access all the elements. +```cpp +for (Value::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr) + printf("%d ", itr->GetInt()); +``` + +And other familar query functions: +* `SizeType Capacity() const` +* `bool Empty() const` + +### Quering Object + +Similarly, we can iterate object members by iterator: + +```cpp +static const char* kTypeNames[] = + { "Null", "False", "True", "Object", "Array", "String", "Number" }; + +for (Value::ConstMemberIterator itr = document.MemberBegin(); + itr != document.MemberEnd(); ++itr) +{ + printf("Type of member %s is %s\n", + itr->name.GetString(), kTypeNames[itr->value.GetType()]); +} +``` + +``` +Type of member hello is String +Type of member t is True +Type of member f is False +Type of member n is Null +Type of member i is Number +Type of member pi is Number +Type of member a is Array +``` + +Note that, when `operator[](const char*)` cannot find the member, it will fail an assertion. + +If we are unsure whether a member exists, we need to call `HasMember()` before calling `operator[](const char*)`. However, this incurs two lookup. A better way is to call `FindMember()`, which can check the existence of member and obtain its value at once: + +```cpp +Value::ConstMemberIerator itr = document.FindMember("hello"); +if (itr != 0) + printf("%s %s\n", itr->value.GetString()); +``` + +### Querying Number + +JSON provide a single numerical type called Number. Number can be integer or real numbers. RFC 4627 says the range of Number is specified by parser. + +As C++ provides several integer and floating point number types, the DOM trys to handle these with widest possible range and good performance. + +When the DOM parses a Number, it stores it as either one of the following type: + +Type | Description +--------------------------------------------------- +`unsigned` | 32-bit unsigned integer +`int` | 32-bit signed integer +`uint64_t` | 64-bit unsigned integer +`int64_t` | 64-bit signed integer +`double` | 64-bit double precision floating point + +When querying a number, you can check whether the number can be obtained as target type: + +Function | Description +----------------------------------------------- +`IsNumber()` | whether the value is a number +`IsInt()` | whether the number is a int +`IsUint()` | whether the number is a uint +`IsInt64()` | whether the number is a int64_t +`IsUint64()` | whether the number is a uint64_t +`IsDouble()` | whether the number is a double + +Note that, an integer value may be obtained in various ways without conversion. For example, A value `x` containing `123` will make `x.IsInt() == x.IsUint() == x.Int64() == x.Uint64() == ture`. But a value `y` containing `-3000000000` will only makes `x.int64() == true`. + +When obtaining the numeric values, `GetDouble()` will convert internal integer representation to a `double`. Note that, `int` and `uint` can be safely convert to `double`, but `int64_t` and `uint64_t` may lose precision (since mantissa of `double` is only 52-bits). + +### Querying String + +In addition to `GetString()`, the `Value` class also contains `GetStringLength()`. Here explains why. + +According to RFC 4627, JSON strings can contain unicode character `U+0000`, which must be escaped as `"\u0000"`. The problem is that, C/C++ often uses null-terminated string, which treats ``\0'` as the terminator symbol. + +To conform RFC 4627, RapidJSON supports string containing `U+0000`. If you need to handle this, you can use `GetStringLength()` API to obtain the correct length of string. + +For example, after parsing a the following JSON string to `Document d`. + +```js +{ "s" : "a\u0000b" } +``` +The correct length of the value `"a\u0000b"` is 3. But `strlen()` returns 1. + +`GetStringLength()` can also improve performance, as user may often need to call `strlen()` for allocating buffer. + +Besides, `std::string` also support a constructor: + +```cpp +string( const char* s, size_type count); +``` + +which accepts the length of string as parameter. This constructor supports storing null character within the string, and should also provide better performance. ## Create/Modify Values -## Object +### Object -## Array +### Array -## String +### String -## Number - -## True/False/Null