第三章 Caché JSON 迭代数组
动态实体使用标准的迭代方法%GetNext()
,该方法同时处理对象和数组。还可以通过按顺序寻址每个元素(使用for
循环或类似的结构)来遍历一个数组,但这需要了解一些数组的知识,其中的元素不包含值。由于%GetNext()
通过跳过这些元素来避免问题,所以它应该是尽可能首选的迭代方法。
使用%GetNext()遍历动态实体
所有动态实体都提供%GetIterator()
方法,该方法返回一个%Iterator
实例(可以是%Iterator
)。包含指向动态对象或数组成员的指针。%Iterator
对象提供%GetNext()
方法来获取每个成员的键和值。
对%GetNext()
方法的每个调用都会向前移动迭代器游标,如果它位于有效成员上,则返回1 (true
);如果位于最后一个成员上,则返回0 (false
)。成员的名称或索引号在第一个输出参数中返回,值在第二个输出参数中返回。例如:
/// d ##class(PHA.OP.MOB.Test).TestIteratingDynamicEntity()
ClassMethod TestIteratingDynamicEntity()
{
set test = ["a","b","c"]
set iter = test.%GetIterator()
while iter.%GetNext(.key, .value) {
write "element:"_key_"=/"_value_"/ "
}
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestIteratingDynamicEntity()
element:0=/a/ element:1=/b/ element:2=/c/
迭代器指针只向一个方向移动;它不能返回到以前的成员或以相反的顺序迭代数组。
当迭代一个数组时,迭代器跳过没有赋值的元素。在遍历对象时,属性不一定以可预测的顺序返回。下面的示例演示了数组迭代和对象迭代之间的这些区别。
遍历数组
本例创建一个数组。该数组有6个元素,但只有元素0、1和5有赋值。JSON字符串中显示的空元素只是未赋值的占位符:
/// d ##class(PHA.OP.MOB.Test).TestIteratingArray()
ClassMethod TestIteratingArray()
{
set dynArray=["abc",999]
set dynArray."5" = "final"
write dynArray.%Size()_" elements: "_dynArray.%ToJSON()
}
DHC-APP> d ##class(PHA.OP.MOB.Test).TestIteratingArray()
6 elements: ["abc",999,null,null,null,"final"]
%GetNext()将只返回三个有值的元素,跳过所有未分配的元素:
/// d ##class(PHA.OP.MOB.Test).TestIteratingArray()
ClassMethod TestIteratingArray()
{
set dynArray=["abc",999]
set dynArray."5" = "final"
write dynArray.%Size()_" elements: "_dynArray.%ToJSON()
set iterator=dynArray.%GetIterator()
while iterator.%GetNext(.key,.val) {
write !, "Element index: "_key_", value: "_val
}
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestIteratingArray()
6 elements: ["abc",999,null,null,null,"final"]
Element index: 0, value: abc
Element index: 1, value: 999
Element index: 5, value: final
遍历对象
对象属性没有固定的顺序,这意味着可以按任意顺序创建和销毁属性,而不需要创建未赋值,但是更改对象也可以更改%GetNext()
返回属性的顺序。下面的示例创建一个具有三个属性的对象,调用%Remove()
销毁一个属性,然后添加另一个属性:
/// d ##class(PHA.OP.MOB.Test).TestIteratingObject()
ClassMethod TestIteratingObject()
{
set dynObject={"propA":"abc","PropB":"byebye","propC":999}
do dynObject.%Remove("PropB")
set dynObject.propD = "final"
write dynObject.%Size()_" properties: "_dynObject.%ToJSON()
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestIteratingObject()
3 properties: {"propA":"abc","propD":"final","propC":999}
当我们遍历对象时,%GetNext()
不会按创建的顺序返回项:
/// d ##class(PHA.OP.MOB.Test).TestIteratingObject()
ClassMethod TestIteratingObject()
{
set dynObject={"propA":"abc","PropB":"byebye","propC":999}
do dynObject.%Remove("PropB")
set dynObject.propD = "final"
write dynObject.%Size()_" properties: "_dynObject.%ToJSON()
set iterator=dynObject.%GetIterator()
while iterator.%GetNext(.key,.val) {
write !, "Property name: """_key_""", value: "_val
}
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestIteratingObject()
3 properties: {"propA":"abc","propD":"final","propC":999}
Property name: "propA", value: abc
Property name: "propD", value: final
Property name: "propC", value: 999
理解数组和未赋值
动态数组可以是数组,这意味着并不是数组的所有元素都包含值。例如,可以将一个值赋给一个动态数组的元素100,即使该数组还没有包含元素0到99。内存中的空间只分配给元素100处的值。元素0到99是未赋值的,这意味着0到99是有效的元素标识符,但不指向内存中的任何值。%Size()
方法将返回101的数组大小,但是%GetNext()
方法将跳过未赋值的元素,只返回元素100中的值。
下面的例子通过给元素8和11赋值来创建一个数组:
/// d ##class(PHA.OP.MOB.Test).TestSparseArrays()
ClassMethod TestSparseArrays()
{
set array = ["val_0",true,1,null,"","val_5"]
do array.%Set(8,"val_8")
set array."11" = "val_11"
write array.%ToJSON()
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestSparseArrays()
["val_0",true,1,null,"","val_5",null,null,"val_8",null,null,"val_11"]
注意:创建数组时,定义位置如果超过了数组长度,可以指定数组长度,其他位置为null
元素5、7、9和10没有分配任何值,它们在内存中不占空间,但是它们在JSON字符串中由null值表示,因为JSON不支持未定义的值。
在数组中使用%Remove
方法%Remove()
像处理任何其他元素一样处理未分配的元素。可以有一个只包含未赋值的数组。下面的示例创建一个数组,然后删除未分配的元素0。然后删除元素7,它现在是唯一一个包含值的元素:
/// d ##class(PHA.OP.MOB.Test).TestSparseArraysRemove()
ClassMethod TestSparseArraysRemove()
{
set array = []
do array.%Set(8,"val_8")
do array.%Remove(0)
do array.%Remove(6)
write "Array size = "_array.%Size()_":",!,array.%ToJSON()
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestSparseArraysRemove()
Array size = 7:
[null,null,null,null,null,null,"val_8"]
注意:JSON不能保留空值和未赋值之间的区别。 动态实体包含元数据,允许它们区分空值和未赋值。JSON没有指定单独的未定义数据类型,因此在将动态实体序列化为JSON字符串时,没有保持这种区别的规范方法。如果不希望序列化数据中有额外的空值,则必须在序列化之前删除未分配的元素,或者使用一些与应用程序相关的方法将这种区别记录为元数据。
使用%Size()的数组迭代
%Size()
的作用是:返回动态实体中属性或元素的数量。例如:
/// d ##class(PHA.OP.MOB.Test).TestObjectSize()
ClassMethod TestObjectSize()
{
set dynObject={"prop1":123,"prop2":[7,8,9],"prop3":{"a":1,"b":2}}
write "Number of properties: "_dynObject.%Size()
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestObjectSize()
Number of properties: 3
在稀疏数组中,这个数字包括未赋值的元素,如下面的示例所示。本例中创建的数组有6个元素,但只有元素0、1和5有赋值。JSON字符串中显示的空元素只是未赋值的占位符:
/// d ##class(PHA.OP.MOB.Test).TestArraySize()
ClassMethod TestArraySize()
{
set test=["abc",999]
set test."5" = "final"
write test.%Size()_" elements: "_test.%ToJSON()
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestArraySize()
6 elements: ["abc",999,null,null,null,"final"]
元素2、3和4没有赋值,但仍然被视为有效的数组元素。动态数组是从零开始的,因此最后一个元素的索引号总是%Size()-1
。
下面的例子以相反的顺序遍历数组test的所有6个元素,并使用%Get()
返回它们的值:
/// d ##class(PHA.OP.MOB.Test).TestArraySize()
ClassMethod TestArraySize()
{
set test=["abc",999]
set test."5" = "final"
write test.%Size()_" elements: "_test.%ToJSON(),!
for i=(test.%Size()-1):-1:0 {write "element "_i_" = /"_test.%Get(i)_"/",!}
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestArraySize()
6 elements: ["abc",999,null,null,null,"final"]
element 5 = /final/
element 4 = //
element 3 = //
element 2 = //
element 1 = /999/
element 0 = /abc/
对于大于%Size()-1
的数字,%Get()
方法将返回“”
(空字符串),对于负数将抛出异常。
注意 :这里显示的迭代技术仅用于特定目的(比如检测数组中未赋值或以相反的顺序遍历数组)。在大多数情况下,应该使用%GetNext()
,它跳过未分配的元素,可以用于动态对象和动态数组。
使用%IsDefined()
测试有效值
%IsDefined()
方法的作用是:测试指定属性名或数组索引号处是否存在值。如果指定的成员有一个值,则该方法返回1 (true
),如果该成员不存在,则返回0 (false
)。对于稀疏数组中没有赋值的元素,它也将返回false
。
如果使用for
循环遍历稀疏数组,则会遇到未赋值的情况。下面的示例创建一个数组,其中前三个元素是JSON null、空字符串和未赋值。for
循环被故意设置为超过数组的末尾,并测试数组索引为4的元素:
/// d ##class(PHA.OP.MOB.Test).TestIsDefined()
ClassMethod TestIsDefined()
{
set dynarray = [null,""]
set dynarray."3" = "final"
write dynarray.%ToJSON(),!
for index = 0:1:4 {
write !,"Element "_index_": "_(dynarray.%IsDefined(index))
}
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestIsDefined()
[null,"",null,"final"]
Element 0: 1
Element 1: 1
Element 2: 0
Element 3: 1
Element 4: 0
%IsDefined()
在两种情况下返回0:元素2没有赋值,元素4不存在。
ObjectScript为JSON空值返回“”
(空字符串),例如本例中的元素0。如果需要测试“”
和null
以及未分配的值,请使用%GetTypeOf()
而不是%IsDefined()
.
注意:正如在前一节中提到的,不应该为迭代使用for
循环,除非在一些不寻常的情况下。在大多数情况下,应该使用%GetNext()
方法,该方法跳过未赋值。
还可以使用%IsDefined()
方法来测试对象属性是否存在。下面的代码使用三个字符串值创建动态数组名,然后使用前两个字符串创建带有属性prop1
和prop2
的对象dynobj
。
/// d ##class(PHA.OP.MOB.Test).TestIsDefinedObject()
ClassMethod TestIsDefinedObject()
{
set names = ["prop1","prop2","noprop"]
set dynobj={}.%Set(names."0",123).%Set(names."1",456)
write dynobj.%ToJSON()
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestIsDefinedObject()
{"prop1":123,"prop2":456}
下面的代码使用%IsDefined()
来确定哪些字符串在dynobj中被用作属性名:
/// d ##class(PHA.OP.MOB.Test).TestIsDefinedObject()
ClassMethod TestIsDefinedObject()
{
set names = ["prop1","prop2","noprop"]
set dynobj={}.%Set(names."0",123).%Set(names."1",456)
write dynobj.%ToJSON()
for name = 0:1:2 {write !,"Property "_names.%Get(name)_": "_(dynobj.%IsDefined(names.%Get(name)))}
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestIsDefinedObject()
{"prop1":123,"prop2":456}
Property prop1: 1
Property prop2: 1
Property noprop: 0
注意:把[]
中的属性作为{}
的属性来赋值
在动态数组中使用%Push
和%Pop
%Push()
和%Pop()
方法仅对动态数组可用。
它们的工作方式与%Set()
和%Remove()
完全相同,只是它们总是添加或删除数组的最后一个元素。例如,下面的代码使用任意一组方法生成相同的结果。
/// d ##class(PHA.OP.MOB.Test).TestPopPush()
ClassMethod TestPopPush()
{
set array = []
do array.%Set(array.%Size(), 123).%Set(array.%Size(), 456)
write "removed "_array.%Remove(array.%Size()-1)_", leaving "_array.%ToJSON(),!
set array = []
do array.%Push(123).%Push(456)
write "removed "_array.%Pop()_", leaving "_array.%ToJSON(),!
}
注意:每次%Set
后%Size
都返回当前数组的长度。
DHC-APP>d ##class(PHA.OP.MOB.Test).TestPopPush()
removed 456, leaving [123]
removed 456, leaving [123]
虽然%Push()
和%Pop()
用于堆栈操作,但是可以通过用%Remove(0)
替换%Pop()
来实现队列。下面的示例使用%Push()
构建一个数组,然后使用%Pop()
以相反的顺序删除每个元素。
使用%Push()
和%Pop()
构建一个数组并销毁
构建一个包含嵌套数组的数组。对%Push()
的最后一次调用指定了一个可选的类型参数,该参数将一个布尔值存储为JSON 0 false,1 true,而不是Caché 0
/// d ##class(PHA.OP.MOB.Test).TestPush()
ClassMethod TestPush()
{
set array=[]
do array.%Push(42).%Push("abc").%Push([])
do array."2".%Push("X").%Push(0,"boolean")
write array.%ToJSON()
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestPush()
[42,"abc",["X",false]]
删除嵌套数组的所有元素。与所有动态实体方法一样,%Pop()
将返回Caché 0,而不是JSON false:
/// d ##class(PHA.OP.MOB.Test).TestPush()
ClassMethod TestPush()
{
set array=[]
do array.%Push(42).%Push("abc").%Push([])
do array."2".%Push("X").%Push(1,"boolean")
write array.%ToJSON(),!
for i=0:1:1 {write "/"_array."2".%Pop()_"/ "}
write array.%ToJSON(),!
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestPush()
[42,"abc",["X",true]]
/1/ /X/ [42,"abc",[]]
现在删除主数组的所有元素,包括空嵌套数组:
/// d ##class(PHA.OP.MOB.Test).TestPush()
ClassMethod TestPush()
{
set array=[]
do array.%Push(42).%Push("abc").%Push([])
do array."2".%Push("X").%Push(1,"boolean")
write array.%ToJSON(),!
for i=0:1:1 {write "/"_array."2".%Pop()_"/ "}
write array.%ToJSON(),!
for i=0:1:2 {write "/"_array.%Pop()_"/ "}
write array.%ToJSON(),!
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestPush()
[42,"abc",["X",true]]
/1/ /X/ [42,"abc",[]]
/2@%Library.DynamicArray/ /abc/ /42/ []
为了简单起见,这些示例使用硬编码的for
循环。
网友评论