Here is some working code that will showcase what I've been talking about.

[pascal]unit uStreamObjects;

interface

uses
SysUtils,
Classes;

type

{ TStreamObject }
TStreamObject = class
public
constructor Create;
destructor Destroy; override;
procedure Save(aStream: TStream); virtual;
procedure Load(aStream: TStream); virtual;
function Size: Integer; virtual;
procedure Assign(aObj: TStreamObject); virtual;
end;

{ TObjectA }
TObjectA = class(TStreamObject)
protected
FData1: Integer;
public
constructor Create;
destructor Destroy; override;
procedure Save(aStream: TStream); override;
procedure Load(aStream: TStream); override;
property Data1: Integer read FData1 write FData1;
end;

{ TObjectB }
TObjectB = class(TObjectA)
protected
FData2: Integer;
public
constructor Create;
destructor Destroy; override;
procedure Save(aStream: TStream); override;
procedure Load(aStream: TStream); override;
property Data2: Integer read FData2 write FData2;
end;


implementation

{ --- TStreamObject ----------------------------------------------------}
constructor TStreamObject.Create;
begin
inherited;
end;

destructor TStreamObject.Destroy;
begin
inherited;
end;

procedure TStreamObject.Save(aStream: TStream);
begin
end;

procedure TStreamObject.Load(aStream: TStream);
begin
end;

function TStreamObject.Size: Integer;
var
stm: TMemoryStream;
begin
stm := TMemoryStream.Create;
try
Save(stm);
Result := stm.Size;
finally
stm.Free;
end;
end;

procedure TStreamObject.Assign(aObj: TStreamObject);
var
Stream: TMemoryStream;
begin
// create memory stream
Stream := TMemoryStream.Create;

try
// check if incoming object is larger
if aObj.Size > Self.Size then
begin
// save to stream
aObj.Save(Stream);

// reset stream position
Stream.Position := 0;

// load in data from stream
Self.Load(Stream);
end
else
begin
// save self data to stream
Self.Save(Stream);

// reset stream position
Stream.Position := 0;

// write incoming data to stream
aObj.Save(Stream);

// reset stream position
Stream.Position := 0;

// load in data from stream
Self.Load(Stream);
end;
finally
Stream.Free;
end;
end;

{ --- TObjectA ---------------------------------------------------------}
constructor TObjectA.Create;
begin
inherited;
FData1 := 0;
end;

destructor TObjectA.Destroy;
begin
inherited;
end;

procedure TObjectA.Save(aStream: TStream);
begin
inherited;
aStream.Write(FData1, SizeOf(FData1));
end;

procedure TObjectA.Load(aStream: TStream);
begin
inherited;
aStream.Read(FData1, SizeOf(FData1));
end;


{ --- TObjectB ---------------------------------------------------------}
constructor TObjectB.Create;
begin
inherited;
FData2 := 0;
end;

destructor TObjectB.Destroy;
begin
inherited;
end;

procedure TObjectB.Save(aStream: TStream);
begin
inherited;
aStream.Write(FData2, SizeOf(FData2));
end;

procedure TObjectB.Load(aStream: TStream);
begin
inherited;
aStream.Read(FData2, SizeOf(FData2));
end;

end.
[/pascal]

Now a small example:

[pascal]var
A: TObjectA;
B: TObjectB;

begin
A := TObjectA.Create;
B := TObjectB.Create;

A.Data1 := 1;

B.Data1 := 2;
B.Data2 := 3;

WriteLn('A.Data1: ', A.Data1);
WriteLn('B.Data1: ', B.Data1);
WriteLn('B.Data2: ', B.Data2);


A.Assign(B);
writeln('after assignment...');
WriteLn('A.Data1: ', A.Data1);
WriteLn('B.Data1: ', B.Data1);
WriteLn('B.Data2: ', B.Data2);


B.Free;
A.Free;

Write('Press ENTER to continue...');
ReadLn;
end.
[/pascal]

A small situation you will need to decide how to handle is the case where B is smaller than A. The data in A beyond B becomes essentially undefined because there maybe a situation where A's data is directly dependent on inherited data which is often the case. Ideally you would clear all the fields of A, do the assignment and then reinit all dependent data moving down the chain.

The assign method presented here will simply try to figure out which object is largest and write that out to the stream first, then over write the stream with the new data and then read this updated stream back into.

Keep me posted on your progress.