unit utcjsontypes; {$mode objfpc}{$H+} interface uses Classes, SysUtils, fpcunit, testutils, testregistry, System.JSON.Types, Generics.Collections, Math; type { TTestJsonLineInfo } TTestJsonLineInfo = class(TTestCase) private FLineInfo: TJsonLineInfo; protected procedure SetUp; override; procedure TearDown; override; published procedure TestGetLineNumber; procedure TestGetLinePosition; procedure TestHasLineInfo; procedure TestLineNumberProperty; procedure TestLinePositionProperty; end; { TTestJsonPosition } TTestJsonPosition = class(TTestCase) private FPosition: TJsonPosition; published procedure TestCreateDefault; procedure TestCreateWithType; procedure TestClear; procedure TestWriteToObject; procedure TestWriteToArray; procedure TestWriteToConstructor; procedure TestBuildPathEmpty; procedure TestBuildPathSingle; procedure TestBuildPathMultiple; procedure TestFormatMessage; end; { TTestJsonFiler } TTestJsonFiler = class(TTestCase) private type TTestJsonFilerImpl = class(TJsonFiler) protected function GetInsideContainer: Boolean; override; end; var FFiler: TTestJsonFilerImpl; protected procedure SetUp; override; procedure TearDown; override; published procedure TestCreateDestroy; procedure TestPushPop; procedure TestPeek; procedure TestGetPath; procedure TestRewind; procedure TestIsEndToken; procedure TestIsStartToken; procedure TestIsPrimitiveToken; end; { TTestJsonOid } TTestJsonOid = class(TTestCase) private FOid: TJsonOid; published procedure TestCreateFromBytes; procedure TestCreateFromString; procedure TestAsString; procedure TestAsBytes; procedure TestStringRoundTrip; procedure TestBytesRoundTrip; procedure TestInvalidStringLength; end; { TTestJsonRegEx } TTestJsonRegEx = class(TTestCase) private FRegEx: TJsonRegEx; published procedure TestCreate; procedure TestAsString; procedure TestSetAsString; procedure TestSetAsStringVariations; end; { TTestJsonDBRef } TTestJsonDBRef = class(TTestCase) private FDBRef: TJsonDBRef; published procedure TestCreateWithDB; procedure TestCreateWithoutDB; procedure TestCreateWithOid; procedure TestAsString; procedure TestSetAsString; end; { TTestJsonCodeWScope } TTestJsonCodeWScope = class(TTestCase) private FCodeWScope: TJsonCodeWScope; published procedure TestCreateEmpty; procedure TestCreateWithScope; end; { TTestJsonDecimal128 } TTestJsonDecimal128 = class(TTestCase) private FDecimal: TJsonDecimal128; published procedure TestCreateFromString; procedure TestCreateFromExtended; procedure TestIsZero; procedure TestIsNan; procedure TestIsPosInfinity; procedure TestIsNegInfinity; procedure TestAsExtended; procedure TestAsString; end; { TTestJsonNameAttribute } TTestJsonNameAttribute = class(TTestCase) private FAttribute: JsonNameAttribute; protected procedure TearDown; override; published procedure TestCreate; procedure TestValue; end; { TTestEJsonException } TTestEJsonException = class(TTestCase) published procedure TestCreateSimple; procedure TestCreateWithInner; procedure TestInnerException; end; implementation { TTestJsonLineInfo } procedure TTestJsonLineInfo.SetUp; begin inherited SetUp; FLineInfo := TJsonLineInfo.Create; end; procedure TTestJsonLineInfo.TearDown; begin FLineInfo.Free; inherited TearDown; end; procedure TTestJsonLineInfo.TestGetLineNumber; begin AssertEquals('Default line number', 0, FLineInfo.GetLineNumber); end; procedure TTestJsonLineInfo.TestGetLinePosition; begin AssertEquals('Default line position', 0, FLineInfo.GetLinePosition); end; procedure TTestJsonLineInfo.TestHasLineInfo; begin AssertFalse('Default has no line info', FLineInfo.HasLineInfo); end; procedure TTestJsonLineInfo.TestLineNumberProperty; begin AssertEquals('Line number property', 0, FLineInfo.LineNumber); end; procedure TTestJsonLineInfo.TestLinePositionProperty; begin AssertEquals('Line position property', 0, FLineInfo.LinePosition); end; { TTestJsonPosition } procedure TTestJsonPosition.TestCreateDefault; begin FPosition := TJsonPosition.Create; AssertEquals('Default container type', Ord(TJsonContainerType.None), Ord(FPosition.ContainerType)); AssertEquals('Default position', -1, FPosition.Position); AssertEquals('Default property name', '', FPosition.PropertyName); AssertFalse('Default has no index', FPosition.HasIndex); end; procedure TTestJsonPosition.TestCreateWithType; begin FPosition := TJsonPosition.Create(TJsonContainerType.&Array); AssertEquals('Array container type', Ord(TJsonContainerType.&Array), Ord(FPosition.ContainerType)); AssertTrue('Array has index', FPosition.HasIndex); AssertEquals('Array position', -1, FPosition.Position); end; procedure TTestJsonPosition.TestClear; begin FPosition.ContainerType := TJsonContainerType.&Object; FPosition.Position := 5; FPosition.PropertyName := 'test'; FPosition.Clear; AssertEquals('Cleared container type', Ord(TJsonContainerType.None), Ord(FPosition.ContainerType)); AssertEquals('Cleared position', -1, FPosition.Position); AssertEquals('Cleared property name', '', FPosition.PropertyName); end; procedure TTestJsonPosition.TestWriteToObject; var Sb: TStringBuilder; begin Sb := TStringBuilder.Create; try FPosition := TJsonPosition.Create(TJsonContainerType.&Object); FPosition.PropertyName := 'test'; FPosition.WriteTo(Sb); AssertEquals('Object path', 'test', Sb.ToString); Sb.Clear; Sb.Append('root'); FPosition.WriteTo(Sb); AssertEquals('Object path with prefix', 'root.test', Sb.ToString); finally Sb.Free; end; end; procedure TTestJsonPosition.TestWriteToArray; var Sb: TStringBuilder; begin Sb := TStringBuilder.Create; try FPosition := TJsonPosition.Create(TJsonContainerType.&Array); FPosition.Position := 5; FPosition.WriteTo(Sb); AssertEquals('Array path', '[5]', Sb.ToString); finally Sb.Free; end; end; procedure TTestJsonPosition.TestWriteToConstructor; var Sb: TStringBuilder; begin Sb := TStringBuilder.Create; try FPosition := TJsonPosition.Create(TJsonContainerType.&Constructor); FPosition.Position := 3; FPosition.WriteTo(Sb); AssertEquals('Constructor path', '[3]', Sb.ToString); finally Sb.Free; end; end; procedure TTestJsonPosition.TestBuildPathEmpty; var Positions: TJsonPositionList; Path: string; begin Positions := TJsonPositionList.Create; try Path := TJsonPosition.BuildPath(Positions); AssertEquals('Empty path', '', Path); finally Positions.Free; end; end; procedure TTestJsonPosition.TestBuildPathSingle; var Positions: TJsonPositionList; Pos: TJsonPosition; Path: string; begin Positions := TJsonPositionList.Create; try Pos := TJsonPosition.Create(TJsonContainerType.&Object); Pos.PropertyName := 'test'; Positions.Add(Pos); Path := TJsonPosition.BuildPath(Positions); AssertEquals('Single object path', 'test', Path); finally Positions.Free; end; end; procedure TTestJsonPosition.TestBuildPathMultiple; var Positions: TJsonPositionList; Pos1, Pos2: TJsonPosition; Path: string; begin Positions := TJsonPositionList.Create; try Pos1 := TJsonPosition.Create(TJsonContainerType.&Object); Pos1.PropertyName := 'root'; Positions.Add(Pos1); Pos2 := TJsonPosition.Create(TJsonContainerType.&Array); Pos2.Position := 0; Positions.Add(Pos2); Path := TJsonPosition.BuildPath(Positions); AssertEquals('Multiple path', 'root[0]', Path); finally Positions.Free; end; end; procedure TTestJsonPosition.TestFormatMessage; var LineInfo: TJsonLineInfo; Msg: string; begin LineInfo := TJsonLineInfo.Create; try Msg := TJsonPosition.FormatMessage(LineInfo, 'test.path', 'Error occurred'); AssertTrue('Message contains error', Pos('Error occurred', Msg) > 0); AssertTrue('Message contains path', Pos('test.path', Msg) > 0); finally LineInfo.Free; end; end; { TTestJsonFiler.TTestJsonFilerImpl } function TTestJsonFiler.TTestJsonFilerImpl.GetInsideContainer: Boolean; begin Result := FCurrentPosition.ContainerType <> TJsonContainerType.None; end; { TTestJsonFiler } procedure TTestJsonFiler.SetUp; begin inherited SetUp; FFiler := TTestJsonFilerImpl.Create; end; procedure TTestJsonFiler.TearDown; begin FFiler.Free; inherited TearDown; end; procedure TTestJsonFiler.TestCreateDestroy; begin AssertNotNull('Filer created', FFiler); AssertEquals('Empty path', '', FFiler.Path); end; procedure TTestJsonFiler.TestPushPop; begin AssertEquals('Initial peek', Ord(TJsonContainerType.None), Ord(FFiler.Peek)); FFiler.Push(TJsonContainerType.&Object); AssertEquals('After push object', Ord(TJsonContainerType.&Object), Ord(FFiler.Peek)); FFiler.Push(TJsonContainerType.&Array); AssertEquals('After push array', Ord(TJsonContainerType.&Array), Ord(FFiler.Peek)); AssertEquals('Pop array', Ord(TJsonContainerType.&Array), Ord(FFiler.Pop)); AssertEquals('After pop array', Ord(TJsonContainerType.&Object), Ord(FFiler.Peek)); AssertEquals('Pop object', Ord(TJsonContainerType.&Object), Ord(FFiler.Pop)); AssertEquals('After pop object', Ord(TJsonContainerType.None), Ord(FFiler.Peek)); end; procedure TTestJsonFiler.TestPeek; begin AssertEquals('Initial peek', Ord(TJsonContainerType.None), Ord(FFiler.Peek)); FFiler.Push(TJsonContainerType.&Object); AssertEquals('Peek object', Ord(TJsonContainerType.&Object), Ord(FFiler.Peek)); AssertEquals('Peek again', Ord(TJsonContainerType.&Object), Ord(FFiler.Peek)); end; procedure TTestJsonFiler.TestGetPath; begin AssertEquals('Empty path', '', FFiler.Path); FFiler.Push(TJsonContainerType.&Object); // Path building requires the position to be set up properly // Since we haven't set any property names, the path should still be empty AssertEquals('Path after push without properties', '', FFiler.Path); end; procedure TTestJsonFiler.TestRewind; begin FFiler.Push(TJsonContainerType.&Object); FFiler.Push(TJsonContainerType.&Array); FFiler.Rewind; AssertEquals('After rewind', Ord(TJsonContainerType.None), Ord(FFiler.Peek)); end; procedure TTestJsonFiler.TestIsEndToken; begin AssertTrue('EndObject is end', TJsonFiler.IsEndToken(TJsonToken.EndObject)); AssertTrue('EndArray is end', TJsonFiler.IsEndToken(TJsonToken.EndArray)); AssertTrue('EndConstructor is end', TJsonFiler.IsEndToken(TJsonToken.EndConstructor)); AssertFalse('StartObject is not end', TJsonFiler.IsEndToken(TJsonToken.StartObject)); AssertFalse('String is not end', TJsonFiler.IsEndToken(TJsonToken.&String)); end; procedure TTestJsonFiler.TestIsStartToken; begin AssertTrue('StartObject is start', TJsonFiler.IsStartToken(TJsonToken.StartObject)); AssertTrue('StartArray is start', TJsonFiler.IsStartToken(TJsonToken.StartArray)); AssertTrue('StartConstructor is start', TJsonFiler.IsStartToken(TJsonToken.StartConstructor)); AssertFalse('EndObject is not start', TJsonFiler.IsStartToken(TJsonToken.EndObject)); AssertFalse('String is not start', TJsonFiler.IsStartToken(TJsonToken.&String)); end; procedure TTestJsonFiler.TestIsPrimitiveToken; begin AssertTrue('Integer is primitive', TJsonFiler.IsPrimitiveToken(TJsonToken.Integer)); AssertTrue('Float is primitive', TJsonFiler.IsPrimitiveToken(TJsonToken.Float)); AssertTrue('String is primitive', TJsonFiler.IsPrimitiveToken(TJsonToken.&String)); AssertTrue('Boolean is primitive', TJsonFiler.IsPrimitiveToken(TJsonToken.Boolean)); AssertTrue('Null is primitive', TJsonFiler.IsPrimitiveToken(TJsonToken.Null)); AssertFalse('StartObject is not primitive', TJsonFiler.IsPrimitiveToken(TJsonToken.StartObject)); AssertFalse('EndObject is not primitive', TJsonFiler.IsPrimitiveToken(TJsonToken.EndObject)); end; { TTestJsonOid } procedure TTestJsonOid.TestCreateFromBytes; var TestBytes: TBytes; begin SetLength(TestBytes, 12); TestBytes[0] := $01; TestBytes[1] := $02; TestBytes[11] := $0C; FOid := TJsonOid.Create(TestBytes); AssertEquals('First byte', $01, FOid.Bytes[0]); AssertEquals('Second byte', $02, FOid.Bytes[1]); AssertEquals('Last byte', $0C, FOid.Bytes[11]); end; procedure TTestJsonOid.TestCreateFromString; begin FOid := TJsonOid.Create('0102030405060708090a0b0c'); AssertEquals('First byte from string', $01, FOid.Bytes[0]); AssertEquals('Second byte from string', $02, FOid.Bytes[1]); AssertEquals('Last byte from string', $0C, FOid.Bytes[11]); end; procedure TTestJsonOid.TestAsString; var TestBytes: TBytes; begin SetLength(TestBytes, 12); TestBytes[0] := $01; TestBytes[1] := $02; TestBytes[11] := $0C; FOid := TJsonOid.Create(TestBytes); AssertEquals('String representation', '01020000000000000000000C', FOid.AsString.ToUpper); end; procedure TTestJsonOid.TestAsBytes; var TestBytes, ResultBytes: TBytes; begin SetLength(TestBytes, 12); TestBytes[0] := $AB; TestBytes[11] := $CD; FOid := TJsonOid.Create(TestBytes); ResultBytes := FOid.AsBytes; AssertEquals('Byte array length', 12, Length(ResultBytes)); AssertEquals('First byte', $AB, ResultBytes[0]); AssertEquals('Last byte', $CD, ResultBytes[11]); end; procedure TTestJsonOid.TestStringRoundTrip; const TestString = '0123456789abcdef01234567'; begin FOid := TJsonOid.Create(TestString); AssertEquals('String round trip', TestString.ToUpper, FOid.AsString.ToUpper); end; procedure TTestJsonOid.TestBytesRoundTrip; var TestBytes, ResultBytes: TBytes; begin SetLength(TestBytes, 12); TestBytes[0] := $12; TestBytes[5] := $34; TestBytes[11] := $56; FOid := TJsonOid.Create(TestBytes); ResultBytes := FOid.AsBytes; AssertEquals('Bytes round trip length', Length(TestBytes), Length(ResultBytes)); AssertEquals('Bytes round trip first', TestBytes[0], ResultBytes[0]); AssertEquals('Bytes round trip middle', TestBytes[5], ResultBytes[5]); AssertEquals('Bytes round trip last', TestBytes[11], ResultBytes[11]); end; procedure TTestJsonOid.TestInvalidStringLength; begin try FOid := TJsonOid.Create('invalid'); Fail('Should have raised exception for invalid string length'); except on E: Exception do AssertTrue('Correct exception type', E is EJsonException); end; end; { TTestJsonRegEx } procedure TTestJsonRegEx.TestCreate; begin FRegEx := TJsonRegEx.Create('test.*', 'gi'); AssertEquals('RegEx pattern', 'test.*', FRegEx.RegEx); AssertEquals('RegEx options', 'gi', FRegEx.Options); end; procedure TTestJsonRegEx.TestAsString; begin FRegEx := TJsonRegEx.Create('test.*', 'gi'); AssertEquals('AsString format', '/test.*/gi', FRegEx.AsString); end; procedure TTestJsonRegEx.TestSetAsString; begin FRegEx.AsString := '/test.*/gi'; AssertEquals('Set regex pattern', 'test.*', FRegEx.RegEx); AssertEquals('Set regex options', 'gi', FRegEx.Options); end; procedure TTestJsonRegEx.TestSetAsStringVariations; begin // Test single part FRegEx.AsString := 'simple'; AssertEquals('Simple regex', 'simple', FRegEx.RegEx); AssertEquals('Simple options', '', FRegEx.Options); // Test two parts FRegEx.AsString := '/pattern'; AssertEquals('Two part regex', 'pattern', FRegEx.RegEx); AssertEquals('Two part options', '', FRegEx.Options); // Test three parts (normal case) FRegEx.AsString := '/pattern/flags'; AssertEquals('Three part regex', 'pattern', FRegEx.RegEx); AssertEquals('Three part options', 'flags', FRegEx.Options); end; { TTestJsonDBRef } procedure TTestJsonDBRef.TestCreateWithDB; begin FDBRef := TJsonDBRef.Create('testdb', 'testcoll', '507f1f77bcf86cd799439011'); AssertEquals('DB name', 'testdb', FDBRef.DB); AssertEquals('Collection name', 'testcoll', FDBRef.Ref); AssertEquals('ID string', '507F1F77BCF86CD799439011', FDBRef.Id.AsString.ToUpper); end; procedure TTestJsonDBRef.TestCreateWithoutDB; begin FDBRef := TJsonDBRef.Create('testcoll', '507f1f77bcf86cd799439011'); AssertEquals('Empty DB name', '', FDBRef.DB); AssertEquals('Collection name', 'testcoll', FDBRef.Ref); AssertEquals('ID string', '507F1F77BCF86CD799439011', FDBRef.Id.AsString.ToUpper); end; procedure TTestJsonDBRef.TestCreateWithOid; var TestOid: TJsonOid; begin TestOid := TJsonOid.Create('507f1f77bcf86cd799439011'); FDBRef := TJsonDBRef.Create('testdb', 'testcoll', TestOid); AssertEquals('DB name with OID', 'testdb', FDBRef.DB); AssertEquals('Collection name with OID', 'testcoll', FDBRef.Ref); AssertEquals('ID from OID', TestOid.AsString.ToUpper, FDBRef.Id.AsString.ToUpper); end; procedure TTestJsonDBRef.TestAsString; begin FDBRef := TJsonDBRef.Create('testdb', 'testcoll', '507f1f77bcf86cd799439011'); AssertEquals('Full string format', 'TESTDB.TESTCOLL.507F1F77BCF86CD799439011', FDBRef.AsString.ToUpper); FDBRef := TJsonDBRef.Create('testcoll', '507f1f77bcf86cd799439011'); AssertEquals('No DB string format', 'TESTCOLL.507F1F77BCF86CD799439011', FDBRef.AsString.ToUpper); end; procedure TTestJsonDBRef.TestSetAsString; begin FDBRef.AsString := 'testdb.testcoll.507f1f77bcf86cd799439011'; AssertEquals('Set DB from string', 'testdb', FDBRef.DB); AssertEquals('Set collection from string', 'testcoll', FDBRef.Ref); FDBRef.AsString := 'testcoll.507f1f77bcf86cd799439011'; AssertEquals('Set empty DB from string', '', FDBRef.DB); AssertEquals('Set collection from short string', 'testcoll', FDBRef.Ref); end; { TTestJsonCodeWScope } procedure TTestJsonCodeWScope.TestCreateEmpty; begin FCodeWScope := TJsonCodeWScope.Create('function() { return 1; }', nil); AssertEquals('Code value', 'function() { return 1; }', FCodeWScope.Code); AssertEquals('Empty scope length', 0, Length(FCodeWScope.Scope)); end; procedure TTestJsonCodeWScope.TestCreateWithScope; var Scope: TStringList; begin Scope := TStringList.Create; try Scope.Add('var1=value1'); Scope.Add('var2=value2'); FCodeWScope := TJsonCodeWScope.Create('function() { return var1 + var2; }', Scope); AssertEquals('Code with scope', 'function() { return var1 + var2; }', FCodeWScope.Code); AssertEquals('Scope length', 2, Length(FCodeWScope.Scope)); AssertEquals('First scope ident', 'var1', FCodeWScope.Scope[0].Ident); AssertEquals('First scope value', 'value1', FCodeWScope.Scope[0].Value); AssertEquals('Second scope ident', 'var2', FCodeWScope.Scope[1].Ident); AssertEquals('Second scope value', 'value2', FCodeWScope.Scope[1].Value); finally Scope.Free; end; end; { TTestJsonDecimal128 } procedure TTestJsonDecimal128.TestCreateFromString; begin // Basic test - actual implementation depends on assigned conversion functions try FDecimal := TJsonDecimal128.Create('123.45'); // If we get here, creation succeeded AssertTrue('Created from string', True); except on EJsonException do // Expected if conversion functions not implemented AssertTrue('Expected exception for unimplemented decimal', True); end; end; procedure TTestJsonDecimal128.TestCreateFromExtended; begin try FDecimal := TJsonDecimal128.Create(123.45); AssertTrue('Created from extended', True); except on EJsonException do AssertTrue('Expected exception for unimplemented decimal', True); end; end; procedure TTestJsonDecimal128.TestIsZero; begin FDecimal.lo := 0; FDecimal.hi := $3040000000000000; AssertTrue('Is zero', FDecimal.IsZero); FDecimal.lo := 1; AssertFalse('Not zero with lo=1', FDecimal.IsZero); end; procedure TTestJsonDecimal128.TestIsNan; begin FDecimal.lo := 0; FDecimal.hi := $7C00000000000000; AssertTrue('Is NaN', FDecimal.IsNan); FDecimal.hi := $7C00000000000001; AssertFalse('Not NaN with different hi', FDecimal.IsNan); end; procedure TTestJsonDecimal128.TestIsPosInfinity; begin FDecimal.lo := 0; FDecimal.hi := $7800000000000000; AssertTrue('Is positive infinity', FDecimal.IsPosInfinity); FDecimal.hi := $7800000000000001; AssertFalse('Not positive infinity with different hi', FDecimal.IsPosInfinity); end; procedure TTestJsonDecimal128.TestIsNegInfinity; begin FDecimal.lo := 0; FDecimal.hi := QWord($F800000000000000); AssertTrue('Is negative infinity', FDecimal.IsNegInfinity); FDecimal.hi := $7800000000000001; AssertFalse('Not negative infinity with different hi', FDecimal.IsNegInfinity); end; procedure TTestJsonDecimal128.TestAsExtended; var Result: Extended; begin // Test zero FDecimal.lo := 0; FDecimal.hi := $3040000000000000; Result := FDecimal.AsExtended; AssertEquals('Zero as extended', 0.0, Result, 0.0001); // Test NaN FDecimal.lo := 0; FDecimal.hi := $7C00000000000000; Result := FDecimal.AsExtended; AssertTrue('NaN as extended', IsNaN(Result)); end; procedure TTestJsonDecimal128.TestAsString; begin try FDecimal.lo := 0; FDecimal.hi := $3040000000000000; // This will likely fail unless conversion functions are set up FDecimal.AsString; AssertTrue('String conversion succeeded', True); except on EJsonException do AssertTrue('Expected exception for unimplemented string conversion', True); end; end; { TTestJsonNameAttribute } procedure TTestJsonNameAttribute.TearDown; begin FAttribute.Free; inherited TearDown; end; procedure TTestJsonNameAttribute.TestCreate; begin FAttribute := JsonNameAttribute.Create('testName'); AssertNotNull('Attribute created', FAttribute); end; procedure TTestJsonNameAttribute.TestValue; begin FAttribute := JsonNameAttribute.Create('testName'); AssertEquals('Attribute value', 'testName', FAttribute.Value); end; { TTestEJsonException } procedure TTestEJsonException.TestCreateSimple; var Ex: EJsonException; begin Ex := EJsonException.Create('Test message'); try AssertEquals('Simple message', 'Test message', Ex.Message); AssertNull('No inner exception', Ex.InnerException); finally Ex.Free; end; end; procedure TTestEJsonException.TestCreateWithInner; var Inner: Exception; Ex: EJsonException; begin Inner := Exception.Create('Inner message'); try Ex := EJsonException.Create('Outer message', Inner); try AssertEquals('Outer message', 'Outer message', Ex.Message); AssertNotNull('Has inner exception', Ex.InnerException); AssertSame('Same inner exception', Inner, Ex.InnerException); finally Ex.Free; end; finally Inner.Free; end; end; procedure TTestEJsonException.TestInnerException; var Inner: Exception; Ex: EJsonException; begin Inner := Exception.Create('Inner message'); try Ex := EJsonException.Create('Outer message', Inner); try AssertEquals('Inner exception message', 'Inner message', Ex.InnerException.Message); finally Ex.Free; end; finally Inner.Free; end; end; initialization RegisterTests([ TTestJsonLineInfo, TTestJsonPosition, TTestJsonFiler, TTestJsonOid, TTestJsonRegEx, TTestJsonDBRef, TTestJsonCodeWScope, TTestJsonDecimal128, TTestJsonNameAttribute, TTestEJsonException ]); end.