Until now, I was only focusing on speed improvements and memory consumption for TAnyValue. But if any of you think this is all I wanted to do with it, then you are wrong. All along I had plans to extend the functionality of TAnyValue and make a true powerhouse out of it. My goal is to make it an efficient all around data carrier and to make a powerful interface on top of it. Something that will blow Variant type and TValue away.

The first major add-on is array handling. Any value now has premium array handling support 🙂 I will cut this short and just go to examples. Let me just mention that not only array is now supported but I added support for many basic types that were still missing and also support for Variants. But there is more to come in the future. Ok to the examples then.

I have defined a new record type in Cromis.AnyValue.pas. Its called TAnyArray and it is a powerful wrapper around TAnyValues which is an dynamic array of TAnyValue and also new. TAnyArray can be used as a standalone variable with no problems, but it is also a part of TAnyValue. Lets say you need a new array stored inside TAnyValue (the type is avtArray). You simply write it like this:

  AnyValue.EnsureAsArray.Create([5, '5', 1.22, AnyValues([nil, MyObject, 6])]);

This created an array with values and additional array inside the first one. Yes arrays can be nested with no limitations. Also each array is again a TAnyValue. Actually the Create does not create the array it just initializes it. The creation comes from EnsureAsArray. For those familiar with my SimpleStorage you know that Ensure returns something or creates a new instance if not already there. In this case it ensures that an array is returned. Also if AnyValue contained some other data type, it clears it and sets the type to avtArray.

Now how do you access the array values. Simple, and actually you can do it two ways. Lets say you want a second element:

  AnyValue[1].AsString;
  AnyValue.GetAsArray.Item[1].AsString;

You can treat AnyValue as TAnyArray. The default property handles that for you. But if you want the more advanced stuff, you can call AnyValue.GetAsArray, which returns the array, or raises exception if AnyValue is not of type avtArray. Array also has a neat function called GetAsString. It returns the whole array as string and can be very useful. Lets say we have an empty array and we want to push some new items into it:

  AnyValue.GetAsArray.Push([5, '4.5', AnyValues([7, '5', 3, AnyValues([1.2, 3, '5'])])]);
  AnyValue.GetAsArray.GetAsString;

The result will be:

5,4.5,[7,5,3[1.2,3,5]]

You can also chose the delimiter. If you want tab for delimiter just call it like this:

  AnyValue.GetAsArray.GetAsString(#9);

Now lets see what other candy the TAnyArray holds

  MaValue := AnyValue.GetAsArray.Pop;
  MyClone := AnyValue.GetAsArray.Clone;
  AnyValue.GetAsArray.Reverse;
  MySlice := AnyValue.GetAsArray.Slice(2,6);
  AnyValue.GetAsArray.Sort
  (
    function(Item1, Item2: PAnyValue): Integer
    begin
      Result := StrToInt64Def(Item1.AsString, -1) - StrToInt64Def(Item2.AsString, -1);
    end
  );
  AnyValue.GetAsArray.Clear;
  AnyValue.GetAsArray.SetCapactiy(10000);
  AnyValue.GetAsArray.IndexOf(5);
  AnyValue.GetAsArray.Contains(5);
  AnyValue.GetAsArray.Delete(5, True);
  AnyValue.GetAsArray.Delete[5, '4', nil];
  AnyValue.GetAsArray.SaveToStream(MyStream);
  AnyValue.GetAsArray.LoadFromStream(MyStream);

As you can see, you can save and load from / to stream without being aware of types, arrays, nested depth, it just works. You can delete all instances of an item, you can delete many items at once, you can slice, clone etc…. All that comes in a lightweight package of a record with support for any type you wish to store in it.

And for the finish let me show named values support which is handled with arrays but each such value is a unique TAnyValue instance with type avtNamedValue and a very flexible hash list also with TAnyValue support.

If you want to have name-value type of variables all you have to do is.

  AnyValue['Delphi'] := 'XE3';

or

  AnyValue.GetAsArray.AddNamed('Delphi', 'XE3');

Both are again the same. Named item is added to the array if it does not yet exist. If it does, only the value is assigned. Value is TAnyValue. Also for all arrays, you have enumeration support:

  for Value in AnyValue do
    // do somehing

or

  for Value in AnyValue.GetAsArray do
    // do somehing

And as a final example, the hash that is much more convenient then the old hash where you could only add pointers and could only have strings for keys. Here both the key and the value are TAnyValue. Furthermore it is very easy to make new type of hashes with different hashing functions. You only derive from TCustomHashTable and override some functions. Lets see how easy it was to make string and cardinal key hashing classes.

  // hash table that uses cardinals as keys
  TCardinalHashTable = class(TCustomHashTable)
  protected
    function DoCalculateHash(const Key: TAnyValue): Cardinal; override;
  public
    constructor Create(const Size: Cardinal = cDefaultHashSize);
  end;
 
  // hash table that uses strings as keys
  TStringHashTable = class(TCustomHashTable)
  protected
    function DoCalculateHash(const Key: TAnyValue): Cardinal; override;
  public
    constructor Create(const Size: Cardinal = cDefaultHashSize);
  end;

The whole implementation is here

{ TCardinalHashTable }
 
constructor TCardinalHashTable.Create(const Size: Cardinal);
begin
  inherited Create(Hash_SimpleCardinalMod, Size);
end;
 
function TCardinalHashTable.DoCalculateHash(const Key: TAnyValue): Cardinal;
var
  KeyData: Cardinal;
begin
  KeyData := Key.AsCardinal;
  Result := FHashFunction(@KeyData, FSize);
end;
 
{ TStringHashTable }
 
constructor TStringHashTable.Create(const Size: Cardinal);
begin
  inherited Create(Hash_SuperFastHash, Size);
end;
 
function TStringHashTable.DoCalculateHash(const Key: TAnyValue): Cardinal;
var
  KeyData: string;
begin
  KeyData := Key.AsString;
 
  if KeyData <> '' then
    Result := FHashFunction(@KeyData[1], Length(KeyData) * SizeOf(Char)) mod FSize
  else
    Result := 0;
end;

Simple isn’t it. You can make whatever type of hash table you want. Also the already available string one uses SuperFastHash from “Davy Landman” You can look the licence for more details.

The use of string hash for example is trivial

  MyHashTable.Add('Delphi', 'XE3');
  MyHashTable.Add('Delphi', 2010);
  MyHashTable.Add('Delphi', nil);
  MyHashTable.Add(2013, 'Integer');
  MyHashTable.Add(4.5, 'Float');
  MyHashTable.Add([4.5, '2'], 'Even arrays would work');

All of this just works, the key is always taken as string and the value is TAnyValue. You see how much power there is in TAnyValue, all in a fast and memory friendly package. And you still retain type safety, every TAnyValue instance carries the value type inside and if you try to convert to something you cannot, an exception is raised.

If you want to see the demo for the arrays and named-values support you can download it here along with the speed test. Cromis.Hashing which contains the hash table classes is already available if you download the whole Cromis Library. I will add TAnyValue and all the new features soon as a separate download, but I still have to write some demo programs and tests to be sure everything works as it should. Meanwhile all constructive criticism and ideas are very welcome.