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 fields
Data.ClassName as a sanity check
rttiType.Name which should be the same as Data.ClassName
- Second breakpoint inside the
for rttiField loop on the if not ShouldMarshalstatement
- Watch or log these values:
rttiType.Name inside the loop, it changes value to match rttiField.Name, because of a debugger bug not showing it as E2171 Variable 'rttiType' inaccessible here due to optimization.
rttiField.Name the actual field name
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 unit REST.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
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^.