Android Bug

No zero based string index in TJsonStreamWriter.Write method for Value string.

Works on windows fails on android and ios.
pChar(Value)^ instead Value[1]


Can you please clarify what you mean? What is the error message you get and what is the code that causes it?

TJSONWriter returns empty Stream in a Android App (not tested on IOS but it should do the same)


example:

procedure TForm1.WindowsButtonClick(Sender: TObject);
var
  Stream: TStream;
begin
  Stream:=TMemoryStream.Create;

  TJsonWriter.Create(Stream)
    .WriteBeginObject
    .WriteName('field1').WriteInteger(1)
    .WriteEndObject
  .Free;

  TMemoryStream(Stream).SaveToFile(TPath.Combine(TPath.GetHomePath,'object.json'));

  FreeAndNil(Stream);
end;

The code in adroid needs some change to make the same :(
but before whe need to change  the line
  System.Move(Value[1], StrBuff[StrBuffLen], LV * SizeOf(char));
to
  System.Move(pChar(Value)^, StrBuff[StrBuffLen], LV * SizeOf(char));
at unit Sparkle.Json.Writer method procedure TJsonStreamWriter.Write(const Value: string);
so Zero and not Zero based strings works fine.

procedure TForm1.AndroidButtonClick(Sender: TObject);
var
  Stream: TStream;
  Writer: TJSONWriter;
begin
  Stream:=TMemoryStream.Create;

  Writer:=TJsonWriter.Create(Stream);
  Writer
    .WriteBeginObject
    .WriteName('field1').WriteInteger(1)
    .WriteEndObject
    .Flush; //flush breaks efluent interface (on android the buffer its not flushed when reference it's destroyed ?)
  Writer:=nil;  //FreeAndNil(Writer) also valid, make object refcount=0 on ARC

  TMemoryStream(Stream).SaveToFile(TPath.Combine(TPath.GetHomePath,'object.json'));
  FreeAndNil(Stream);
end;

That's how it is, blame Delphi compiler for that.

In Android, due to ARC, the object is not destroyed.  JsonWriter automatically flushed data when it's destroyed, but in Android, it's not destroyed unless you explicitly do so. Or call Flush. It's always safe to call Flush if you are doing cross platform development, exactly because of that, to avoid relying on the object being destroyed at some exact point.

About the line you suggest to change in Sparkle.Json.Writer,  did you test it and confirm that it makes a difference? That fix should not be necessary, Sparkle code for both Windows and Android use 1-based strings.

For the second question Value[1] was #0 and Value[0]='{' (at BeginWriteObject) so copy from Value[1] to StrBuffLen puts a #0 at the first char in StrBuff. sparkle.inc define {$ZEROBASEDSTRINGS OFF} but on Debug's seems this doesn't work.

About the second question ARC on then WindowsButtonClick or AndroidButtonClik I make ARC=0 before acceding the stream, so I suppose that android would destroy asynchronously and that’s why flush it's not called.

So the suggestion about this to leave one version it's leave flush to be effluent (return a instance to himself, so I can connect flush to free so I don't need to create a variable to reference to.

 

Best regards

Alexander Brazda

Ok we can make Flush fluent. About the string issue, so after you correctly call Flush, are you sure the code you pasted fails if your change is not implemented? 

DO you get different results between DEBUG and RELEASE configs? What Delphi version are you using?
Hi 
I test release and debug version with flush and without correction it works fine. It seems that debugger always shows strings as zero based so inspection of values offers different results that code.

 I'm using RAD Studio XE8 Enterprise, compiled with Delphi personality

Regards
Alexander Brazda

I make Flush fluent and test the same code for Windows and android version works fine, so I can use reader without any variable reference.

Reader per heaps could be extended so ReadInteger, ReadString, etc can have a name parameter so exception can be raised if ReadName result differs from expected, and keep using fluent.

Assert(Reader.ReadName='field1');
x:=Reader.ReadInteger;
Assert(Reader.ReadName='field2');
y:=Reader.ReadInteger;
 
to 
x:=Reader.ReadInteger('field1');
y:=Reader.ReadInteger('field2');
or 
.ReadInteger('field1',x)
.ReadInteger('field2',y)
.Flush
.Free