Browse Source

Refactor `JSONPointer`

Renames `Segment` into `Component`, allows to "Pop" segments without
removing them from the caller `JSONPointer`.
pull/8/head
Anton Tarasenko 3 years ago
parent
commit
bdf2ca5ff8
  1. 20
      sources/Data/Collections/Collection.uc
  2. 1
      sources/Data/Collections/Tests/TEST_CollectionsMixed.uc
  3. 56
      sources/Text/JSON/JSONAPI.uc
  4. 385
      sources/Text/JSON/JSONPointer.uc
  5. 139
      sources/Text/Tests/TEST_JSON.uc

20
sources/Data/Collections/Collection.uc

@ -64,11 +64,13 @@ public final function Iter Iterate()
* Returns stored `AcediaObject` from the caller storage
* (or from it's sub-storages) via given `Text` path.
*
* Path is used in one of the two ways:
* 1. If path is an empty `Text` or if it starts with "/" character,
* it will be interpreted as
* a [JSON pointer](https://tools.ietf.org/html/rfc6901);
* 2. Otherwise it will be used as an argument's name.
* Path is treated like a [JSON pointer](https://tools.ietf.org/html/rfc6901).
* If given path does not start with "/" character (like it is expected from
* a json pointer) - it will be added automatically.
* This means that "foo/bar" is treated like "/foo/bar" and
* "path" like "/path". However, empty `Text` is treated like itself (""),
* since it constitutes a valid JSON pointer (it will point at a caller
* collection itself).
*
* Acedia provides two collections:
* 1. `DynamicArray` is treated as a JSON array in the context of
@ -91,9 +93,7 @@ public final function Iter Iterate()
* this method (i.e. `AssociativeArray` only lets you access values with
* `Text` keys).
*
* @param jsonPointerAsText Treated as a JSON pointer if it starts with "/"
* character or is an empty `Text`, otherwise treated as an item's
* name / identificator inside the caller collection.
* @param jsonPointerAsText Path, given by a JSON pointer.
* @return An item `jsonPointerAsText` is referring to (according to the above
* stated rules). `none` if such item does not exist.
*/
@ -112,7 +112,7 @@ public final function AcediaObject GetItemByPointer(Text jsonPointerAsText)
nextCollection = self;
while (segmentIndex < pointer.GetLength() - 1)
{
nextSegment = pointer.GetSegment(segmentIndex);
nextSegment = pointer.GetComponent(segmentIndex);
nextCollection = Collection(nextCollection.GetByText(nextSegment));
_.memory.Free(nextSegment);
if (nextCollection == none) {
@ -122,7 +122,7 @@ public final function AcediaObject GetItemByPointer(Text jsonPointerAsText)
}
if (nextCollection != none)
{
nextSegment = pointer.GetSegment(segmentIndex);
nextSegment = pointer.GetComponent(segmentIndex);
result = nextCollection.GetByText(nextSegment);
_.memory.Free(nextSegment);
}

1
sources/Data/Collections/Tests/TEST_CollectionsMixed.uc

@ -56,7 +56,6 @@ protected static function Test_GetByPointer()
DynamicArray(obj.GetItemByPointer(P("/innerObject/array"))));
Issue("`GetItemByPointer()` does not return `none` for incorrect pointers");
TEST_ExpectNone(obj.GetItemByPointer(P("innerObject/array")));
TEST_ExpectNone(obj.GetItemByPointer(P("//")));
TEST_ExpectNone(obj.GetItemByPointer(P("/innerObject/array/5")));
TEST_ExpectNone(obj.GetItemByPointer(P("/innerObject/array/-1")));

56
sources/Text/JSON/JSONAPI.uc

@ -64,23 +64,53 @@ private final function InitFormatting()
}
/**
* Creates new `JSONPointer` from a given text representation `pointerAsText`.
*
* @param pointerAsText Treated as a JSON pointer if it starts with "/"
* character or is an empty `Text`, otherwise treated as an item's
* name / identificator inside the caller collection (without resolving
* escaped sequences "~0" and "~1").
* @return `JSONPointer` if passed `Text` was not `none`. `none` otherwise.
* Creates new `JSONPointer`, corresponding to a given path in
* JSON pointer format (https://tools.ietf.org/html/rfc6901).
*
* If provided `Text` value is an incorrect pointer, then it will be
* treated like an empty pointer.
* However, if given pointer can be fixed by prepending "/" - it will be
* done automatically. This means that "foo/bar" is treated like "/foo/bar",
* "path" like "/path", but empty `Text` "" is treated like itself.
*
* @param pointerAsText `Text` representation of the JSON pointer.
* @return New `JSONPointer`, corresponding to the given `pointerAsText`.
* Guaranteed to not be `none`. If provided `pointerAsText` is
* an incorrect JSON pointer or `none`, - empty `JSONPointer` will be
* returned.
*/
public final function JSONPointer Pointer(Text pointerAsText)
{
local JSONPointer pointer;
pointer = JSONPointer(_.memory.Allocate(class'JSONPointer'));
if (pointer.Initialize(pointerAsText)) {
return pointer;
return JSONPointer(_.memory.Allocate(class'JSONPointer'))
.Set(pointerAsText);
}
/**
* Checks whether passed `AcediaObject` can be converted into JSON by this API.
*
* Compatible objects are `none` and any object that has one of the following
* classes: `BoolBox`, `BoolRef`, `ByteBox`, `ByteRef`, `IntBox`, `IntRef`,
* `FloatBox`, `FloatRef`, `Text`, `MutableText`, `DynamicArray`,
* `AssociativeArray`.
*
* This method does not check whether objects stored inside `DynamicArray`,
* `AssociativeArray` are compatible. If they are not, they will normally be
* defaulted to JSON null upon any conversion.
*/
public function bool IsCompatible(AcediaObject data)
{
local class<AcediaObject> dataClass;
if (data == none) {
return true;
}
pointer.FreeSelf();
return none;
dataClass = data.class;
return dataClass == class'BoolBox' || dataClass == class'BoolRef'
|| dataClass == class'ByteBox' || dataClass == class'ByteRef'
|| dataClass == class'IntBox' || dataClass == class'IntRef'
|| dataClass == class'FloatBox' || dataClass == class'FloatRef'
|| dataClass == class'Text' || dataClass == class'MutableText'
|| dataClass == class'DynamicArray'
|| dataClass == class'AssociativeArray';
}
/**

385
sources/Text/JSON/JSONPointer.uc

@ -1,8 +1,10 @@
/**
* Class for representing a JSON pointer (see
* https://tools.ietf.org/html/rfc6901).
* Allows quick and simple access to parts/segments of it's path.
* Objects of this class should only be used after initialization.
* Allows quick and simple access to components of it's path:
* Path "/a/b/c" will be stored as a sequence of components "a", "b" and "c",
* path "/" will be stored as a singular empty component ""
* and empty path "" would mean that there is not components at all.
* Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
@ -22,105 +24,366 @@
*/
class JSONPointer extends AcediaObject;
var private bool initialized;
// Component of the pointer (the part, separated by slash character '/').
struct Component
{
// For arrays, a component is specified by a numeric index;
// To avoid parsing `asText` multiple times we record whether we
// have already done so.
var bool testedForBeingNumeric;
// Numeric index, represented by `asText`;
// `-1` if it was already tested and found to be equal to not be a number
// (valid index values are always `>= 0`).
var int asNumber;
// `Text` representation of the component.
// Can be equal to `none` only if this component was specified via
// numeric index (guarantees `testedForBeingNumeric == true`).
var MutableText asText;
};
// Segments of the path this JSON pointer was initialized with
var private array<MutableText> keys;
var private array<Component> components;
var protected const int TSLASH, TJSON_ESCAPE, TJSON_ESCAPED_SLASH;
var protected const int TJSON_ESCAPED_ESCAPE;
protected function Finalizer()
{
_.memory.FreeMany(keys);
keys.length = 0;
initialized = false;
Empty();
}
/**
* Initializes caller `JSONPointer` with a given path.
*
* @param pointerAsText Treated as a JSON pointer if it starts with "/"
* character or is an empty `Text`, otherwise treated as an item's
* name / identificator inside the caller collection (without resolving
* escaped sequences "~0" and "~1").
* @return `true` if caller `JSONPointer` was correctly initialized with this
* call. `false` otherwise: can happen if `none` was passed as a parameter
* or caller `JSONPointer` was already initialized.
* Checks whether caller `JSONPointer` is empty (points at the root value).
*
* @return `true` iff caller `JSONPointer` points at the root value.
*/
public final function bool Initialize(Text pointerAsText)
public final function bool IsEmpty()
{
return components.length == 0;
}
/**
* Resets caller `JSONPointer`, erasing all of it's components.
*
* @return Caller `JSONPointer` to allow for method chaining.
*/
public final function JSONPointer Empty()
{
local int i;
local bool hasEscapedSequences;
if (initialized) return false;
if (pointerAsText == none) return false;
for (i = 0; i < components.length; i += 1) {
_.memory.Free(components[i].asText);
}
components.length = 0;
return self;
}
initialized = true;
if (!pointerAsText.StartsWith(T(TSLASH)) && !pointerAsText.IsEmpty()) {
keys[0] = pointerAsText.MutableCopy();
/**
* Sets caller `JSONPointer` to correspond to a given path in
* JSON pointer format (https://tools.ietf.org/html/rfc6901).
*
* If provided `Text` value is an incorrect pointer, then it will be
* treated like an empty pointer.
* However, if given pointer can be fixed by prepending "/" - it will be
* done automatically. This means that "foo/bar" is treated like "/foo/bar",
* "path" like "/path", but empty `Text` "" is treated like itself.
*
* @param pointerAsText `Text` representation of the JSON pointer.
* @return Reference to the caller `JSONPointer` to allow for method chaining.
*/
public final function JSONPointer Set(Text pointerAsText)
{
local int i;
local bool hasEscapedSequences;
local Component nextComponent;
local array<MutableText> parts;
Empty();
if (pointerAsText == none) {
return self;
}
else
{
hasEscapedSequences = (pointerAsText.IndexOf(T(TJSON_ESCAPE)) >= 0);
keys = pointerAsText.SplitByCharacter(T(TSLASH).GetCharacter(0));
parts = pointerAsText.SplitByCharacter(T(TSLASH).GetCharacter(0));
// First elements of the array will be empty, so throw it away
_.memory.Free(keys[0]);
keys.Remove(0, 1);
}
if (!hasEscapedSequences) {
return true;
if (parts[0].IsEmpty())
{
_.memory.Free(parts[0]);
parts.Remove(0, 1);
}
if (hasEscapedSequences)
{
// Replace escaped sequences "~0" and "~1".
// Order is specific, necessity of which is explained in
// JSON Pointer's documentation:
// https://tools.ietf.org/html/rfc6901
for (i = 0; i < keys.length; i += 1)
for (i = 0; i < parts.length; i += 1)
{
parts[i].Replace(T(TJSON_ESCAPED_SLASH), T(TSLASH));
parts[i].Replace(T(TJSON_ESCAPED_ESCAPE), T(TJSON_ESCAPE));
}
}
for (i = 0; i < parts.length; i += 1)
{
keys[i].Replace(T(TJSON_ESCAPED_SLASH), T(TSLASH));
keys[i].Replace(T(TJSON_ESCAPED_ESCAPE), T(TJSON_ESCAPE));
nextComponent.asText = parts[i];
components[components.length] = nextComponent;
}
return true;
return self;
}
/**
* Returns a segment of the path by it's index.
*
* For path "/a/b/c":
* `GetSegment(0) == "a"`
* `GetSegment(1) == "b"`
* `GetSegment(2) == "c"`
* `GetSegment(3) == none`
* For path "/":
* `GetSegment(0) == ""`
* `GetSegment(1) == none`
* For path "":
* `GetSegment(0) == none`
* For path "abc":
* `GetSegment(0) == "abc"`
* `GetSegment(1) == none`
*
* @param index Index of the segment to return. Must be inside
* Adds new component to the caller `JSONPointer`.
*
* Adding component "new" to the pointer representing path "/a/b/c" would
* result in it representing a path "/a/b/c/new".
*
* Although this method can be used to add numeric components, `PushNumeric()`
* should be used for that if possible.
*
* @param newComponent Component to add. If passed `none` value -
* no changes will be made at all.
* @return Reference to the caller `JSONPointer` to allow for method chaining.
*/
public final function JSONPointer Push(Text newComponent)
{
local Component newComponentRecord;
if (newComponent == none) {
return self;
}
newComponentRecord.asText = newComponent.MutableCopy();
components[components.length] = newComponentRecord;
return self;
}
/**
* Adds new numeric component to the caller `JSONPointer`.
*
* Adding component `7` to the pointer representing path "/a/b/c" would
* result in it representing a path "/a/b/c/7".
*
* @param newComponent Numeric component to add. If passed negative value -
* no changes will be made at all.
* @return Reference to the caller `JSONPointer` to allow for method chaining.
*/
public final function JSONPointer PushNumeric(int newComponent)
{
local Component newComponentRecord;
if (newComponent < 0) {
return self;
}
newComponentRecord.asNumber = newComponent;
// Obviously this component is going to be numeric
newComponentRecord.testedForBeingNumeric = true;
components[components.length] = newComponentRecord;
return self;
}
/**
* Removes and returns last component from the caller `JSONPointer`.
*
* In `JSONPointer` corresponding to "/ab/c/d" this method would return "d"
* and leave caller `JSONPointer` to correspond to "/ab/c".
*
* @param doNotRemove Set this to `true` if you want to return last component
* without changing caller pointer.
* @return Last component of the caller `JSONPointer`.
* `none` iff caller `JSONPointer` is empty.
*/
public final function Text Pop(optional bool doNotRemove)
{
local int lastIndex;
local Text result;
if (components.length <= 0) {
return none;
}
lastIndex = components.length - 1;
// Do not use `GetComponent()` to avoid unnecessary `Text` copying
if (components[lastIndex].asText == none) {
result = _.text.FromIntM(components[lastIndex].asNumber);
}
else {
result = components[lastIndex].asText;
}
if (!doNotRemove) {
components.length = components.length - 1;
}
return result;
}
/**
* Removes and returns last numeric component from the caller `JSONPointer`.
*
* In `JSONPointer` corresponding to "/ab/c/7" this method would return `7`
* and leave caller `JSONPointer` to correspond to "/ab/c".
*
* Component is removed regardless of whether it was actually numeric.
*
* @param doNotRemove Set this to `true` if you want to return last component
* without changing caller pointer.
* @return Last component of the caller `JSONPointer`.
* `-1` iff caller `JSONPointer` is empty or last component is not numeric.
*/
public final function int PopNumeric(optional bool doNotRemove)
{
local int lastIndex;
local int result;
if (components.length <= 0) {
return -1;
}
lastIndex = components.length - 1;
result = GetNumericComponent(lastIndex);
_.memory.Free(components[lastIndex].asText);
if (!doNotRemove) {
components.length = components.length - 1;
}
return result;
}
/**
* Returns a component of the path by it's index, starting from `0`.
*
* @param index Index of the component to return. Must be inside
* `[0; GetLength() - 1]` segment.
* @return Path's segment as a `Text`. If passed `index` is outside of
* @return Path's component as a `Text`. If passed `index` is outside of
* `[0; GetLength() - 1]` segment - returns `none`.
*/
public final function Text GetSegment(int index)
public final function Text GetComponent(int index)
{
if (index < 0) return none;
if (index >= keys.length) return none;
if (keys[index] == none) return none;
return keys[index].Copy();
if (index >= components.length) return none;
// `asText` will store `none` only if we have added this component as
// numeric one
if (components[index].asText == none) {
components[index].asText = _.text.FromIntM(components[index].asNumber);
}
return components[index].asText.Copy();
}
/**
* Returns a numeric component of the path by it's index, starting from `0`.
*
* @param index Index of the component to return. Must be inside
* `[0; GetLength() - 1]` segment and correspond to numeric comonent.
* @return Path's component as a `Text`. If passed `index` is outside of
* `[0; GetLength() - 1]` segment or does not correspond to
* a numeric component - returns `-1`.
*/
public final function int GetNumericComponent(int index)
{
local Parser parser;
if (index < 0) return -1;
if (index >= components.length) return -1;
if (!components[index].testedForBeingNumeric)
{
components[index].testedForBeingNumeric = true;
parser = _.text.Parse(components[index].asText);
parser.MUnsignedInteger(components[index].asNumber);
if (!parser.Ok() || !parser.HasFinished()) {
components[index].asNumber = -1;
}
parser.FreeSelf();
}
return components[index].asNumber;
}
/**
* Converts caller `JSONPointer` into it's `Text` representation.
*
* For the method, but returning `MutableText` see `ToTextM()`.
*
* @return `Text` that represents caller `JSONPointer`.
*/
public final function Text ToText()
{
local Text result;
local MutableText builder;
builder = ToTextM();
result = builder.Copy();
builder.FreeSelf();
return result;
}
/**
* Converts caller `JSONPointer` into it's `MutableText` representation.
*
* For the method, but returning `Text` see `ToTextM()`.
*
* @return `MutableText` that represents caller `JSONPointer`.
*/
public final function MutableText ToTextM()
{
local int i;
local Text nextComponent;
local MutableText nextMutableComponent;
local MutableText result;
result = _.text.Empty();
if (GetLength() <= 0) {
return result;
}
for (i = 0; i < GetLength(); i += 1)
{
nextComponent = GetComponent(i);
nextMutableComponent = nextComponent.MutableCopy();
// Replace (order is important)
nextMutableComponent.Replace(T(TJSON_ESCAPE), T(TJSON_ESCAPED_ESCAPE));
nextMutableComponent.Replace(T(TSLASH), T(TJSON_ESCAPED_SLASH));
result.Append(T(TSLASH)).Append(nextMutableComponent);
// Get rid of temporary values
nextMutableComponent.FreeSelf();
nextComponent.FreeSelf();
}
return result;
}
/**
* Amount of path segments in this JSON pointer.
* Amount of path components in the caller `JSONPointer`.
*
* For more details see `GetSegment()`.
* Also see `GetFoldsAmount()` method.
*
* @return Amount of segments in the caller `JSONPointer`.
* @return Amount of components in the caller `JSONPointer`.
*/
public final function int GetLength()
{
return keys.length;
return components.length;
}
/**
* Amount of path components in the caller `JSONPointer` that do not directly
* correspond to a pointed value.
*
* Equal to the `Max(0, GetLength() - 1)`.
*
* For example, path "/user/Ivan/records/5/count" refers to the value named
* "value" that is _folded_ inside `4` objects named "users", "Ivan",
* "records" and "5". Therefore it's folds amount if `4`.
*
* @return Amount of components in the caller `JSONPointer` that do not
* directly correspond to a pointed value.
*/
public final function int GetFoldsAmount()
{
return Max(0, components.length - 1);
}
/**
* Makes an exact copy of the caller `JSONPointer`.
*
* @return Copy of the caller `JSONPointer`.
*/
public final function JSONPointer Copy()
{
local int i;
local JSONPointer newPointer;
local array<Component> newComponents;
newComponents = components;
for (i = 0; i < newComponents.length; i += 1)
{
if (newComponents[i].asText != none) {
newComponents[i].asText = newComponents[i].asText.MutableCopy();
}
}
newPointer = JSONPointer(_.memory.Allocate(class'JSONPointer'));
newPointer.components = newComponents;
return newPointer;
}
defaultproperties

139
sources/Text/Tests/TEST_JSON.uc

@ -30,33 +30,138 @@ protected static function TESTS()
}
protected static function Test_Pointer()
{
Context("Testing method for working with JSON pointers.");
SubTest_PointerCreate();
SubTest_PointerToText();
SubTest_PointerPushPop();
SubTest_PointerNumeric();
}
protected static function SubTest_PointerCreate()
{
local JSONPointer pointer;
Context("Testing JSON pointer.");
Issue("\"Empty\" JSON pointers are not handled correctly.");
pointer = __().json.Pointer(P(""));
TEST_ExpectTrue(pointer.GetLength() == 0);
TEST_ExpectNone(pointer.GetSegment(0));
TEST_ExpectNone(pointer.GetComponent(0));
pointer = __().json.Pointer(P("/"));
TEST_ExpectTrue(pointer.GetLength() == 1);
TEST_ExpectNotNone(pointer.GetSegment(0));
TEST_ExpectTrue(pointer.GetSegment(0).IsEmpty());
TEST_ExpectNotNone(pointer.GetComponent(0));
TEST_ExpectTrue(pointer.GetComponent(0).IsEmpty());
Issue("Normal JSON pointers are not handled correctly.");
pointer = __().json.Pointer(P("/a~1b/c%d/e^f/g|h/i\\j/m~0n"));
TEST_ExpectTrue(pointer.GetLength() == 6);
TEST_ExpectTrue(pointer.GetSegment(0).ToPlainString() == "a/b");
TEST_ExpectTrue(pointer.GetSegment(1).ToPlainString() == "c%d");
TEST_ExpectTrue(pointer.GetSegment(2).ToPlainString() == "e^f");
TEST_ExpectTrue(pointer.GetSegment(3).ToPlainString() == "g|h");
TEST_ExpectTrue(pointer.GetSegment(4).ToPlainString() == "i\\j");
TEST_ExpectTrue(pointer.GetSegment(5).ToPlainString() == "m~n");
Issue("Non-JSON pointers `Text` constants are not handled correctly.");
pointer = __().json.Pointer(P("/a~1b/c%d/e^f//g|h/i\\j/m~0n/"));
TEST_ExpectTrue(pointer.GetLength() == 8);
TEST_ExpectTrue(pointer.GetComponent(0).ToPlainString() == "a/b");
TEST_ExpectTrue(pointer.GetComponent(1).ToPlainString() == "c%d");
TEST_ExpectTrue(pointer.GetComponent(2).ToPlainString() == "e^f");
TEST_ExpectTrue(pointer.GetComponent(3).ToPlainString() == "");
TEST_ExpectTrue(pointer.GetComponent(4).ToPlainString() == "g|h");
TEST_ExpectTrue(pointer.GetComponent(5).ToPlainString() == "i\\j");
TEST_ExpectTrue(pointer.GetComponent(6).ToPlainString() == "m~n");
TEST_ExpectTrue(pointer.GetComponent(7).ToPlainString() == "");
Issue("Initializing JSON pointers with values, not starting with \"/\","
@ "is not handled correctly.");
pointer = __().json.Pointer(P("huh/send~0/pics~1"));
TEST_ExpectTrue(pointer.GetLength() == 1);
TEST_ExpectTrue( pointer.GetSegment(0).ToPlainString()
== "huh/send~0/pics~1");
TEST_ExpectTrue(pointer.GetLength() == 3);
TEST_ExpectTrue(pointer.GetComponent(0).ToPlainString() == "huh");
TEST_ExpectTrue(pointer.GetComponent(1).ToPlainString() == "send~");
TEST_ExpectTrue(pointer.GetComponent(2).ToPlainString() == "pics/");
}
protected static function SubTest_PointerToText()
{
local JSONPointer pointer;
Issue("`JSONPointer` is not converted to `Text` correctly.");
pointer = __().json.Pointer(P(""));
TEST_ExpectTrue(pointer.ToText().ToPlainString() == "");
TEST_ExpectTrue(pointer.ToTextM().ToPlainString() == "");
pointer = __().json.Pointer(P("///"));
TEST_ExpectTrue(pointer.ToText().ToPlainString() == "///");
TEST_ExpectTrue(pointer.ToTextM().ToPlainString() == "///");
pointer = __().json.Pointer(P("/a~1b/c%d/e^f//g|h/i\\j/m~0n/"));
TEST_ExpectTrue( pointer.ToText().ToPlainString()
== "/a~1b/c%d/e^f//g|h/i\\j/m~0n/");
TEST_ExpectTrue( pointer.ToTextM().ToPlainString()
== "/a~1b/c%d/e^f//g|h/i\\j/m~0n/");
pointer = __().json.Pointer(P("/a/b/c"));
Issue("Result of `ToText()` has a wrong class.");
TEST_ExpectTrue(pointer.ToText().class == class'Text');
Issue("Result of `ToTextM()` has a wrong class.");
TEST_ExpectTrue(pointer.ToTextM().class == class'MutableText');
}
protected static function SubTest_PointerPushPop()
{
local JSONPointer pointer;
local Text value0, value1, value2, value3, value4, value5, value6;
Issue("`Push()`/`PushNumeric()` incorrectly affect `JSONPointer`.");
pointer = __().json.Pointer(P("//lets/go"));
pointer.Push(P("one")).PushNumeric(404).Push(P("More"));
TEST_ExpectTrue( pointer.ToText().ToPlainString()
== "//lets/go/one/404/More");
Issue("`Pop()` incorrectly affects `JSONPointer`.");
value6 = pointer.Pop();
TEST_ExpectTrue(pointer.ToText().ToPlainString() == "//lets/go/one/404");
value5 = pointer.Pop();
TEST_ExpectTrue(pointer.ToText().ToPlainString() == "//lets/go/one");
value4 = pointer.Pop();
TEST_ExpectTrue(pointer.ToText().ToPlainString() == "//lets/go");
value3 = pointer.Pop();
TEST_ExpectTrue(pointer.ToText().ToPlainString() == "//lets");
value2 = pointer.Pop();
TEST_ExpectTrue(pointer.ToText().ToPlainString() == "/");
value1 = pointer.Pop();
TEST_ExpectTrue(pointer.ToText().ToPlainString() == "");
value0 = pointer.Pop();
Issue("`Pop()` returns incorrect value.");
TEST_ExpectTrue(value6.ToPlainString() == "More");
TEST_ExpectTrue(value5.ToPlainString() == "404");
TEST_ExpectTrue(value4.ToPlainString() == "one");
TEST_ExpectTrue(value3.ToPlainString() == "go");
TEST_ExpectTrue(value2.ToPlainString() == "lets");
TEST_ExpectTrue(value1.ToPlainString() == "");
TEST_ExpectNone(value0);
}
protected static function SubTest_PointerNumeric()
{
local JSONPointer pointer;
local string correct, incorrect;
correct = "`GetNumericComponent()`/`PopNumeric()` cannot correctly retrieve"
@ "`JSONPointer`'s numeric components.";
incorrect = "`GetNumericComponent()`/`PopNumeric()` do not return negative"
@ "values for non-numeric components `JSONPointer`'s"
@ "numeric components.";
Issue(correct);
pointer = __().json.Pointer(P("/lets//404/8./6/11/d/0"));
pointer.PushNumeric(-2).PushNumeric(13);
TEST_ExpectTrue(pointer.GetNumericComponent(8) == 13);
Issue(incorrect);
TEST_ExpectTrue(pointer.GetNumericComponent(6) < 0);
Issue(correct);
TEST_ExpectTrue(pointer.PopNumeric() == 13);
TEST_ExpectTrue(pointer.PopNumeric() == 0);
Issue(incorrect);
TEST_ExpectTrue(pointer.PopNumeric() < 0);
Issue(correct);
TEST_ExpectTrue(pointer.PopNumeric() == 11);
TEST_ExpectTrue(pointer.PopNumeric() == 6);
Issue(incorrect);
TEST_ExpectTrue(pointer.PopNumeric() < 0);
Issue(correct);
TEST_ExpectTrue(pointer.PopNumeric() == 404);
Issue(incorrect);
TEST_ExpectTrue(pointer.PopNumeric() < 0);
TEST_ExpectTrue(pointer.PopNumeric() < 0);
TEST_ExpectTrue(pointer.PopNumeric() < 0);
TEST_ExpectTrue(pointer.PopNumeric() < 0);
}
protected static function Test_Print()

Loading…
Cancel
Save