vfbb 285 Posted January 20, 2021 (edited) A very simple way to consume one Rest Api, just declaring one interface, based on .Net Refit: https://github.com/viniciusfbb/ipub-refit Edited January 21, 2021 by vfbb 3 Share this post Link to post
Attila Kovacs 629 Posted January 20, 2021 (edited) "but it is likely to work on several previous versions, probably XE7 or newer" I don't think so as I can't find System.JSON.Serializers under Berlin but under Tokyo. (Also lacks from the lib dir) Am I missing this from my installation or is it first appeared in the Tokyo release? Is it possible to decorate the custom nullable fields that this internal serializer/reverter can translate them? A one-time-registered type converter/reverter would be even better. Edited January 20, 2021 by Attila Kovacs Share this post Link to post
vfbb 285 Posted January 20, 2021 21 minutes ago, Attila Kovacs said: I don't think so as I can't find System.JSON.Serializers under Berlin but under Tokyo. I don't have older versions installed to be able to test, or do backwards compatibility, but it looks like you're right, delphi 10.2 tokyo or newer. 22 minutes ago, Attila Kovacs said: Is it possible to decorate the custom nullable fields that this internal serializer/reverter can translate them? A one-time-registered type converter/reverter would be even better. What exactly did you want? The json serialization of delphi is very malleable, you do not need to declare all the necessary fields and you can also use all existing attributes in System.Json.Serializers.pas. 29 minutes ago, Attila Kovacs said: A one-time-registered type converter/reverter would be even better. It's a great option, I will implement it today. Share this post Link to post
Attila Kovacs 629 Posted January 20, 2021 (edited) Either they forgot to ship System.Json.Serializers.pas for Berlin, or was not finished to the time, I've checked 2 installations and it's missing in both! I'm still fumbling around with REST.JsonReflect.pas I'm freaking out right now, the header says "Copyright(c) 2016-2018". Edited January 20, 2021 by Attila Kovacs Share this post Link to post
Attila Kovacs 629 Posted January 20, 2021 (edited) 25 minutes ago, vfbb said: It's a great option, I will implement it today. This is it. You don't have to. System.JSON.Serializers has a JsonConverter() attribute for it. You can even easily implement that only the fields which are - changed or not null or whatever - should be marshalled. Brrrrr. Looks like I was wrong on that. Looks like it's still a decorating-horror. I'll need more investigation on the code. Edited January 20, 2021 by Attila Kovacs Share this post Link to post
Attila Kovacs 629 Posted January 20, 2021 (edited) TJsonDynamicContractResolver.SetTypeConverter Okay, everything clear now. This unit is based on the very same called in .net. But .net has nullable types and they also have: Quote How to ignore a property in class if null, using json.net? -If you create the serializer yourself there is a NullValueHandling property which you can set to ignore. Well, and what do we have? Yet Another Useless Serializer. I'll stick with my modified REST.JsonReflect.pas where I can register converters and reverters on PTypeInfo base. Edited January 20, 2021 by Attila Kovacs Share this post Link to post
vfbb 285 Posted January 21, 2021 @Attila Kovacs I added support to register custom json converters. About the nullables, is very easy to implement the nullable and the converters. See one simple solution: uses System.Rtti, System.TypInfo, System.JSON.Serializers, System.JSON.Readers, System.JSON.Writers, System.JSON.Types, iPub.Rtl.Refit; type TNullable<T> = record IsNull: Boolean; Value: T; end; TNullableConverter<T> = class(TJsonConverter) public procedure WriteJson(const AWriter: TJsonWriter; const AValue: TValue; const ASerializer: TJsonSerializer); override; function ReadJson(const AReader: TJsonReader; ATypeInf: PTypeInfo; const AExistingValue: TValue; const ASerializer: TJsonSerializer): TValue; override; function CanConvert(ATypeInf: PTypeInfo): Boolean; override; end; { TNullableConverter<T> } function TNullableConverter<T>.CanConvert(ATypeInf: PTypeInfo): Boolean; begin Result := ATypeInf = TypeInfo(TNullable<T>); end; function TNullableConverter<T>.ReadJson(const AReader: TJsonReader; ATypeInf: PTypeInfo; const AExistingValue: TValue; const ASerializer: TJsonSerializer): TValue; var LNullable: TNullable<T>; begin if AReader.TokenType = TJsonToken.Null then begin LNullable.IsNull := True; LNullable.Value := Default(T); end else begin LNullable.IsNull := False; LNullable.Value := AReader.Value.AsType<T>; end; TValue.Make<TNullable<T>>(LNullable, Result); end; procedure TNullableConverter<T>.WriteJson(const AWriter: TJsonWriter; const AValue: TValue; const ASerializer: TJsonSerializer); var LNullable: TNullable<T>; LValue: TValue; begin LNullable := AValue.AsType<TNullable<T>>; if LNullable.IsNull then AWriter.WriteNull else begin TValue.Make<T>(LNullable.Value, LValue); AWriter.WriteValue(LValue); end; end; initialization GRestService.RegisterConverters([TNullableConverter<string>, TNullableConverter<Byte>, TNullableConverter<Word>, TNullableConverter<Integer>, TNullableConverter<Cardinal>, TNullableConverter<Single>, TNullableConverter<Double>, TNullableConverter<Int64>, TNullableConverter<UInt64>, TNullableConverter<TDateTime>, TNullableConverter<Boolean>, TNullableConverter<Char>]); end. To use just replace the simple types to the nullable types: TUser = record Name: TNullable<string>; Location: string; Id: Integer; Email: TNullable<string>; end; [BaseUrl('https://api.github.com')] IGithubApi = interface(IipRestApi) ['{4C3B546F-216D-46D9-8E7D-0009C0771064}'] [Get('/users/{user}')] function GetUser(const AUser: string): TUser; end; procedure TForm1.FormCreate(Sender: TObject); var LGithubApi: IGithubApi; LUser: TUser; begin LGithubApi := GRestService.&For<IGithubApi>; LUser := LGithubApi.GetUser('viniciusfbb'); end; You can test the code above and see the user name is not null but the user email is null. The nullable type and the nullable converter code in this example need to be implemented by the developer, I can't put it on the library because each developer has it own implementation os nullable. 1 1 Share this post Link to post
vfbb 285 Posted January 21, 2021 @Attila Kovacs About the version of delphi supported, according to docwiki, the units System.JSON.XXX was introduced in Delphi 10 Seattle, but again I can’t guarantee it’s compatible as it would need to be tested on these versions. 1 Share this post Link to post
Attila Kovacs 629 Posted January 21, 2021 I don't know, I don't think that it was in Seattle, as I said, it's not in Berlin. At least I can see now, that you can pass converters. I could not figure it out before, no time to check it now either. But it looks good, there are only two more things coming to my mind which is necessary to handle all kind of json rest services. -To be able to suppress the null values from the nullables (or a shouldmarshal callback/wallpaper) -To be able to send and receive pure arrays "[]" , some php api's are working that strange way Then it will be a really really nice allrounder. Share this post Link to post
vfbb 285 Posted January 21, 2021 4 minutes ago, Attila Kovacs said: -To be able to send and receive pure arrays "[]" , some php api's are working that strange way Works perfectly in both cases, with the [] or with {..},{..},{..}. In both cases your function result should be a TArray<> of a record or a class. 8 minutes ago, Attila Kovacs said: -To be able to suppress the null values from the nullables (or a shouldmarshal callback/wallpaper) Do you mean what happens with the nullable fields that were not received? Well, all fields are set to the default value before reading the received json, so there are several possible workarounds to ensure that the field not received is considered null. In sydney just add the class operator Initialize to set IsNull true, but in older versions, you can simply change the name of IsNull to IsValid, since the boolean fields started False, then the initial value of IsValid of nullable will be False. But there are other possibilities. Share this post Link to post
Attila Kovacs 629 Posted January 21, 2021 The other way around. Sometimes you just have to send the values you want to change and if you send a "null" for a field the servers clears it's value on the server side too. See from this .net Q/A: Quote How to ignore a property in class if null, using json.net? -If you create the serializer yourself there is a NullValueHandling property which you can set to ignore. btw. TValue.Make<T> is 10.4+ Share this post Link to post
vfbb 285 Posted January 21, 2021 (edited) @Attila Kovacs Here one example without TValue.Make<T>, and with the nullables stating with IsNull True: uses System.Rtti, System.TypInfo, System.JSON.Serializers, System.JSON.Readers, System.JSON.Writers, System.JSON.Types, iPub.Rtl.Refit; type TNullable<T> = record strict private FIsNotNull: Boolean; function GetIsNull: Boolean; procedure SetIsNull(AValue: Boolean); public Value: T; property IsNull: Boolean read GetIsNull write SetIsNull; end; TNullableConverter<T> = class(TJsonConverter) public procedure WriteJson(const AWriter: TJsonWriter; const AValue: TValue; const ASerializer: TJsonSerializer); override; function ReadJson(const AReader: TJsonReader; ATypeInf: PTypeInfo; const AExistingValue: TValue; const ASerializer: TJsonSerializer): TValue; override; function CanConvert(ATypeInf: PTypeInfo): Boolean; override; end; { TNullable<T> } function TNullable<T>.GetIsNull: Boolean; begin Result := not FIsNotNull; end; procedure TNullable<T>.SetIsNull(AValue: Boolean); begin FIsNotNull := not AValue; end; { TNullableConverter<T> } function TNullableConverter<T>.CanConvert(ATypeInf: PTypeInfo): Boolean; begin Result := ATypeInf = TypeInfo(TNullable<T>); end; function TNullableConverter<T>.ReadJson(const AReader: TJsonReader; ATypeInf: PTypeInfo; const AExistingValue: TValue; const ASerializer: TJsonSerializer): TValue; var LNullable: TNullable<T>; begin if AReader.TokenType = TJsonToken.Null then begin LNullable.IsNull := True; LNullable.Value := Default(T); end else begin LNullable.IsNull := False; LNullable.Value := AReader.Value.AsType<T>; end; TValue.Make(@LNullable, TypeInfo(TNullable<T>), Result); end; procedure TNullableConverter<T>.WriteJson(const AWriter: TJsonWriter; const AValue: TValue; const ASerializer: TJsonSerializer); var LNullable: TNullable<T>; LValue: TValue; begin LNullable := AValue.AsType<TNullable<T>>; if LNullable.IsNull then AWriter.WriteNull else begin TValue.Make(@LNullable.Value, TypeInfo(T), LValue); AWriter.WriteValue(LValue); end; end; initialization GRestService.RegisterConverters([TNullableConverter<string>, TNullableConverter<Byte>, TNullableConverter<Word>, TNullableConverter<Integer>, TNullableConverter<Cardinal>, TNullableConverter<Single>, TNullableConverter<Double>, TNullableConverter<Int64>, TNullableConverter<UInt64>, TNullableConverter<TDateTime>, TNullableConverter<Boolean>, TNullableConverter<Char>]); end. To test: TUser = record Name: TNullable<string>; Location: string; Id: Integer; Email: TNullable<string>; NonexistentField: TNullable<Integer>; end; [BaseUrl('https://api.github.com')] IGithubApi = interface(IipRestApi) ['{4C3B546F-216D-46D9-8E7D-0009C0771064}'] [Get('/users/{user}')] function GetUser(const AUser: string): TUser; end; procedure TForm1.FormCreate(Sender: TObject); var LGithubApi: IGithubApi; LUser: TUser; begin LGithubApi := GRestService.&For<IGithubApi>; LUser := LGithubApi.GetUser('viniciusfbb'); end; Edited January 21, 2021 by vfbb Share this post Link to post
Attila Kovacs 629 Posted January 21, 2021 (edited) 2 hours ago, vfbb said: and with the nullables stating with IsNull True: Sorry I don't know what you mean and how to test serialization with an example of deserialization. There should be an option for nullables if "null" values should be serialized or not at all, but you will find this out by yourself one day if you start using this lib with php API's. Anyway, I tried to compile the lib with Berlin and there are couple of things which are only Tokyo+. (Very far from "perhaps XE7"). (Edit: even in Tokyo, System.JSON.Serializers is so buggy that it needs several workarounds) But System.JSON.Serializers.pas compiles fine under Berlin, thanks for bringing this unit to my attention. Edited January 21, 2021 by Attila Kovacs 1 Share this post Link to post
mvanrijnen 123 Posted January 21, 2021 (edited) Ah ok, its for creating an API consumer, (my first thought was something for declaring API's,, e.g. like in radserver (which we are using right now)). Edited January 21, 2021 by mvanrijnen 1 Share this post Link to post
vfbb 285 Posted January 21, 2021 @Attila Kovacs You are right about compatibility, thanks for checking this out. I will put this observation. In the future I can do some backwards compatibility, but not now. About the null values serialization, this library is just to consumes one rest api, then the serialization can occur just when you call a Post or Put and have the parameter ABody that is a record or a class or an array. In this specific case, the library will serialize the type as json, and in this case, the values that is null will be written to the json body without skip. The current implementation of System.JSON.Serializers don’t permit to ignore the fields with null values. 1 hour ago, mvanrijnen said: Ah ok, its for creating an API consumer, (my first thought was something for declaring API's,, e.g. like in radserver (which we are using right now)). Sorry i'll make it clearer 1 Share this post Link to post
Attila Kovacs 629 Posted January 21, 2021 1 hour ago, vfbb said: The current implementation of System.JSON.Serializers don’t permit to ignore the fields with null values Wow, that was an unexpected twist. I hope it's already on the wish list or a bug report was filed as unsatisfactory mimicking of .net. Share this post Link to post