How to debug Delphi JSON export stack-overflows: watch the fields and their circular references
Unlike Delphi RTL XML support which is property based, the JSON support is field based.
By default, JSON uses all fields (no matter their protection level, so anything from strict private
to published
is taken into account).
When there are cycles, they are not detected: it will just stack-overflow with a high set of entries like this:
REST.JsonReflect.{REST.JsonReflect}TTypeMarshaller.MarshalSimpleField($788BFB40,$78AB0150) REST.JsonReflect.{REST.JsonReflect}TTypeMarshaller.MarshalData($78AB0150) REST.JsonReflect.{REST.JsonReflect}TTypeMarshaller.MarshalValue((($A9B7E8, Pointer($AFE168) as IValueData, 80, 336, 2024472912, $78AB0150, TClass($78AB0150), 80, 336, 2024472912, 2,77471682335019e+34, 1,00022251675539e-314, 0,00000000737961e-4933, 2024472912, 202447,2912, 2024472912, 2024472912, ($78AB0150, nil), $78AB0150)),???) REST.JsonReflect.{REST.JsonReflect}TTypeMarshaller.MarshalSimpleField($78A921D0,$78AA69C0) REST.JsonReflect.{REST.JsonReflect}TTypeMarshaller.MarshalData($78AA69C0) REST.JsonReflect.{REST.JsonReflect}TTypeMarshaller.Marshal(???)
The easiest way to debug this is to:
- Set breakpoints in
procedure TTypeMarshaller<TSerial>.MarshalData(Data: TObject);
- First breakpoint on the
for rttiField
loop- Watch or log these values (the first two usually are the same, the last two too):
ComposeTypeName(Data)
which gives you the fully qualified name (including unit and class) of the type exposing the fieldsData.ClassName
as a sanity checkrttiType.Name
which should be the same asData.ClassName
- Watch or log these values (the first two usually are the same, the last two too):
- Second breakpoint inside the
for rttiField
loop on theif not ShouldMarshal
statement- Watch or log these values:
rttiType.Name
inside the loop, it changes value to matchrttiField.Name
, because of a debugger bug not showing it asE2171 Variable 'rttiType' inaccessible here due to optimization
.rttiField.Name
the actual field name
- Watch or log these values:
- First breakpoint on the
Tricks to circumvent circular references:
- remember that fields with a
reference to function
value are not marshaled, so they are an excellent way of shoehorning in a dependency in (the reference value will be a capture which includes the instance data of the function to be called) - applying a
[JsonMarshalled(False)]
attribute (be sure to use unitREST.Json.Types
!) only works when used inside non-generic types:- a class like
TMySoapHeaderValue<T: IInterface> = class
will not expose these attributes - a class like
TMySoapHeaderValue = class
will expose these attributes
- a class like
You can check the JsonMarshalled
problem by setting a breakpoint inside function LazyLoadAttributes(var P: PByte): TFunc<TArray<TCustomAttribute>>;
on the line Exit(nil);
and watch the value for Handle^
.
Leave Your Comment