Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deep Object Comparison Delphi

Looking for a way in Delphi to do deep object comparison for me, preferably 2010 RTTI based as my objects don't inherit from TComponent. I'm developing a test framework in DUnit and need something solid which will point out exactly which field is causing problems (serialization comparison leaves it a bit vague).

like image 477
Barry Avatar asked Nov 03 '11 11:11

Barry


2 Answers

Sort of solved this myself, implemented as a class helper for TObject so can be used everywhere if people want it. D2010 and up due to RTTI but you may be able to convert it to use original RTTI stuff.

Code below may be buggy as originally mine was for DUnit and had lots of checks in it instead of changing the result and doesn't support TCollections or a load of other special cases but can be adapted for that by using the if-elseif-then switch in the middle.

If you have any suggestions and additions please don't hesitate to comment so I can add them to it so other people can use this.

Have fun coding

Barry

unit TObjectHelpers;

interface
   uses classes, rtti;

type

TObjectHelpers = class Helper for TObject
  function DeepEquals (const aObject : TObject) : boolean;
end;

implementation

uses sysutils, typinfo;

{ TObjectHelpers }

function TObjectHelpers.DeepEquals(const aObject: TObject): boolean;
var
  c : TRttiContext;
  t : TRttiType;
  p : TRttiProperty;
begin

  result := true;

  if self = aObject then
    exit; // Equal as same pointer

  if (self = nil) and (aObject = nil) then
    exit; // equal as both non instanced

  if (self = nil) and (aObject <> nil) then
  begin
    result := false;
    exit; // one nil other non nil fail
  end;

  if (self <> nil) and (aObject = nil) then
  begin
     result := false;
     exit; // one nil other non nil fail
  end;

  if self.ClassType <> aObject.ClassType then
  begin
     result := false;
     exit;
  end;

  c := TRttiContext.Create;
  try
    t := c.GetType(aObject.ClassType);

    for p in t.GetProperties do
    begin

       if ((p.GetValue(self).IsObject)) then
       begin

          if not TObject(p.GetValue(self).AsObject).DeepEquals(TObject(p.GetValue(aObject).AsObject)) then
          begin
      result := false;
      exit;
    end;

  end
  else if AnsiSameText(p.PropertyType.Name, 'DateTime') or AnsiSameText(p.PropertyType.Name, 'TDateTime') then
  begin

    if p.GetValue(self).AsExtended <> p.GetValue(aObject).AsExtended then
    begin
      result := false;
      exit;
    end;

  end
  else if AnsiSameText(p.PropertyType.Name, 'Boolean') then
  begin

    if p.GetValue(self).AsBoolean <> p.GetValue(aObject).AsBoolean then
    begin
      result := false;
      exit;
    end;

  end
  else if AnsiSameText(p.PropertyType.Name, 'Currency') then
  begin

     if p.GetValue(self).AsExtended <> p.GetValue(aObject).AsExtended then
     begin
        result := false;
        exit;
     end;

  end
  else if p.PropertyType.TypeKind = tkInteger then
  begin

    if p.GetValue(self).AsInteger <> p.GetValue(aObject).AsInteger then
    begin
      result := false;
      exit;
    end;

  end
  else if p.PropertyType.TypeKind = tkInt64 then
  begin

    if p.GetValue(self).AsInt64 <> p.GetValue(aObject).AsInt64  then
    begin
      result := false;
      exit;
    end;

  end
  else if p.PropertyType.TypeKind = tkEnumeration then
  begin

    if p.GetValue(self).AsOrdinal <> p.GetValue(aObject).AsOrdinal then
    begin
      result := false;
      exit;
    end;

  end
  else
  begin

    if p.GetValue(self).AsVariant <> p.GetValue(aObject).AsVariant then
    begin
      result := false;
      exit;
    end;

  end;

end;

 finally
   c.Free;
  end;

 end;

 end.
like image 97
Barry Avatar answered Sep 21 '22 12:09

Barry


Consider using OmniXML persistence.

For XML differencing, I have written a utility using OmniXML that will do an XML diff, and there are many XML comparison tools out there.

I used OmniXML to do an XML differencing tool for exactly this purpose, and it worked great for me. Unfortunately that tool contains many domain specific things and is closed-source and belongs to a former employer so I cannot post the code.

My comparision tool had a simple algorithm:

  1. Match and build a map of Object1->Object2 node links between matching XML nodes.
  2. Sort every node on a primary key (domain specific knowledge) making XML order unimportant. Since you are not only comparing TComponents with Names, you will need to find a way to establish every objects identity if you want to be able to compare it.
  3. Report items in xml doc 1 that are not in xml doc 2.
  4. Report items in xml doc 2 that are not in xml doc 1.
  5. Report items in xml doc 1 with subkeys or attributes different than xml doc2.
  6. visual tool used two Virtual Tree View controls, and worked a lot like KDIFF3 but as a treeview.
like image 29
Warren P Avatar answered Sep 21 '22 12:09

Warren P