How to debug Delphi JSON export stack-overflows: watch the fields and their circular references
Posted by jpluimers on 2020/12/23
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 rttiFieldloop- 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.ClassNameas a sanity checkrttiType.Namewhich 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 rttiFieldloop on theif not ShouldMarshalstatement- Watch or log these values:
rttiType.Nameinside 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.Namethe actual field name
- Watch or log these values:
- First breakpoint on the
Tricks to circumvent circular references:
- remember that fields with a
reference to functionvalue 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> = classwill not expose these attributes - a class like
TMySoapHeaderValue = classwill 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 a comment