You are not logged in.
Pages: 1
How can I get a record with JSON data in the structure of "array of array of double" or in JSON "[[number, number]]" ?
Every try with RegisterCustomJSONSerializerFromText in Delphi 2007 (best Delphi ever;-) fails.
Is it possible without using TDocVariantData?
Sample JSON data:
{
"type": "LineString",
"coordinates": [[13.3568, 52.6183], [13.4167, 52.5581], [13.3467, 52.5412], [13.3192, 52.5302]]
}
type
TGeometry = packed record
coordinates: array of array of double; // better for me: array of TPoint2D (=record lng,lat:double end;)
AType: RawUTF8;
end;
here my negativ tested definitions:
const
__TGeometry ='coordinates array of array of double AType RawUTF8';
__TGeometry ='coordinates [[double]] AType RawUTF8';
__TGeometry ='coordinates array of [double] AType RawUTF8';
__TGeometry ='coordinates array of array[0..1] of double AType RawUTF8';
TTextWriter.RegisterCustomJSONSerializerFromText(TypeInfo(TGeometry), __TGeometry ).
Options := [soReadIgnoreUnknownFields, soWriteHumanReadable]; // => exception
Offline
One option is to create a record to store coordinates.
type
TCoordinate = packed record
d1: Double;
d2: Double;
end;
TGeometry = packed record
coordinates: array of TCoordinate;
AType: RawUTF8;
end;
const
__TCoordinate = 'd1 double d2 double';
__TGeometry = 'coordinates array of TCoordinate AType RawUTF8';
Register both:
TTextWriter.RegisterCustomJSONSerializerFromText([
TypeInfo(TCoordinate), __TCoordinate,
TypeInfo(TGeometry), __TGeometry
]);
JSON produced:
{"coordinates":[{"d1":13.3568,"d2":52.6183},{"d1":0,"d2":52.5581}],"AType":"test"}'
Offline
This fails with "Unregistered ptCustom for TJSONRecordTextDefinition.AddItem(: TDOUBLEDYNARRAY).
@macfly: I want to read the json data format [[13.3568, 52.6183], [... with RegisterCustomJSONSerializerFromText
I'm afraid I have to do it with TDocVariantData.
Offline
If you cannot modify the json structure, then you will have another problem with the "type" property, which is a reserved word.
Offline
If you cannot modify the json structure, then you will have another problem with the "type" property, which is a reserved word.
Excuse me if I misunderstood smth. Wouldn't &type work then?
Offline
macfly wrote:If you cannot modify the json structure, then you will have another problem with the "type" property, which is a reserved word.
Excuse me if I misunderstood smth. Wouldn't &type work then?
It does not work for me. How does the text definition?
'type RawUtf8' shouldn't work?
Offline
I meant that Delphi reserved word "type" can be declared as "&type" property in the record
TGeometry = packed record
coordinates: ...;
&type: RawUTF8;
end;
I was just trying to say that reserved word shouldn't be a problem. Probably I just misunderstood something about it, don't mind.
Anyway, it seems that @robs is working with GeoJSON. I was interested in that because I'm working with it on my actual project.
Offline
...
I was just trying to say that reserved word shouldn't be a problem. Probably I just misunderstood something about it, don't mind.
My bad. I forgot to change "AType" in text definition, and thought that mORMot did not support reserved words in decoding.
I tested it now and it works perfectly.
Regarding the original question of the post, it seems that there is really no way to do this, since the parser only accepts "array of" simple or record types.
So "array of array of" will not work.
And "array of TDoubleDynArray" will not be accepted.
Offline
About of the subject of compatibility and reserved words.
Is there an option to auto escape the name of tables and columns in queries?
For example TSQLOrder will generate "Select ... From ORDER ...."
And will not be accepted without escape in table name.
Offline
I will test this.
As the database is SQLite I need to check if using as external will not modify any existing behavior.
Offline
Regarding the original question of the post, it seems that there is really no way to do this, since the parser only accepts "array of" simple or record types.
Well, something always can be done it depends on the needs. For example, such a workaround can be considered
Records definitions:
TPoint2D = packed record
lng, lat: double;
needed_for_rtti: RawUTF8;
end;
TGeometry = packed record
coordinates: array of TPoint2D;
&type: RawUTF8;
end;
Custom serialization:
TTextWriter.RegisterCustomJSONSerializer(TypeInfo(TPoint2D), TGeometryCustom.TPoint2DReader,
TGeometryCustom.TPoint2DWriter);
Custom writer:
class procedure TGeometryCustom.TPoint2DWriter(const aWriter: TTextWriter; const aValue);
var
LCoord: TPoint2D absolute aValue;
begin
aWriter.Add('[% , %]', [LCoord.lng, LCoord.lat], twJSONEscape);
end;
Custom reader:
class function TGeometryCustom.TPoint2DReader(P: PUTF8Char; var aValue; out aValid: Boolean;
CustomVariantOptions: PDocVariantOptions): PUTF8Char;
var
LCoord: TPoint2D absolute aValue;
LArray: TDoubleDynArray;
begin
aValid := false;
result := nil;
if (P = nil) or (P^ <> '[') then
exit;
inc(P);
{ LCoord.lng := GetNextItemDouble(P);
LCoord.lat := GetNextItemDouble(P); - doesn't work, don't know why}
DynArrayLoadJSON(LArray, FormatUTF8('[%]', [GetNextItem(P, ']')]), TypeInfo(TDoubleDynArray));
if Length(LArray) > 1 then
begin
LCoord.lng := LArray[0];
LCoord.lat := LArray[1];
end;
Finalize(LArray);
if P = nil then
exit;
aValid := true;
result := P; // ',' or ']' for last item of array
end;
GetNextItemDouble didn't work as I expected for the second value - probably I misunderstood it. So I used an additional dynarray.
I haven't got deep in all that records custom parsing stuff (prefer objects) - my almost first quick experience, but at least it seems working Hope it'll help @robs
Offline
You don't need to put a RawUTF8 within the record.
Don't register the record, but the dynamic array of record.
Then you can write TypeInfo(TPoint2DDynArray).
TPoint2D = packed record
lng, lat: double;
end;
TPoint2DDynArray = array of TPoint2D;
TGeometry = packed record
coordinates: TPoint2DDynArray;
&type: RawUTF8;
end;
TTextWriter.RegisterCustomJSONSerializer(TypeInfo(TPoint2DDynArray), TGeometryCustom.TPoint2DReader,
TGeometryCustom.TPoint2DWriter);
And you don't need custom serialization, you can use plain text serialization with the dynamic array.
Offline
You don't need to put a RawUTF8 within the record.
Agree, it left there from another earlier try. needed_for_rtti can be easily removed.
Don't register the record, but the dynamic array of record.
Then you can write TypeInfo(TPoint2DDynArray).
Yeah, also thought so, but somehow it didn't work for me. Maybe I have mistaken somewhere. But it doesn't make much difference as far as I understand. At least custom reader/writer will be the same, I guess.
And you don't need custom serialization, you can use plain text serialization with the dynamic array.
a dynamic array of records? wouldn't "lng"/"lat" names appear then in output?
Last edited by Vitaly (2021-01-07 18:54:32)
Offline
Sorry, but I don't understand how text-based definition (if we're talking about it) can result [[13.3568, 52.6183],...] from TGeometry/TPoint2D(lng,lat:double) records.
Anyway, it makes me feel more and more stupid I guess I should leave the discussion for saving my poor mental health
Offline
Regarding the original question of the post, it seems that there is really no way to do this, since the parser only accepts "array of" simple or record types.
Yes, I thought he could create a custom serialization, but from the way he asked the question it seemed that he only wanted based on text definition.
I believe that via custom serialization is the only option.
And you don't need custom serialization, you can use plain text serialization with the dynamic array.
As I understand he cannot change the structure of JSON.
Via text definition seems that is not possible to obtain the same structure that he expects.
Last edited by macfly (2021-01-07 19:39:07)
Offline
Vitaly, your solution works fine... and makes me more and more smarter;-)
Any idea how to parse in dependence of geometry type (geojson)?
"Point" => TPoint2D ([13.4578, 52.6037])
"LineString" => array of TPoint2D (my json example)
"Polygon" => array of array of TPoint2D "[[[13.3142, 52.5014], [13.3103, 52.6055], [13.3326, 52.5558], [13.3142, 52.5014]]]"
...
In the callback function, the data must be read (or write) in differently depending on the "type". Alternatively, the coordinates could be delivered as json-string and evaluated separately.
You don't need to put a RawUTF8 within the record.
Without RawUTF8 in TPoint2D my Delphi2007 compiler fails with "E2134 Typ besitzt keine Typinformation" (missing TypeInfo)
&type works fine. JSON read and write looks fine.
LCoord.lng := GetNextItemDouble(P);
LCoord.lat := GetNextItemDouble(P, ']'); // this works.
Thanks to all!
Offline
Vitaly, your solution works fine... and makes me more and more smarter;-)
Glad to hear that
In the callback function, the data must be read (or write) in differently depending on the "type". Alternatively, the coordinates could be delivered as json-string and evaluated separately.
Yes, I also came to these two paths. I haven't found the best approach yet (that's why I was particularly interested in this thread), but for the temporary solution, I decided to parse coordinates on-demand ("alternative way", as you described). The reason is that I have to work with rather big .geojson files (up to 400 Mb) and I do not need all GeoJSON objects coordinates at once. I do not need serialization in GeoJSON format, so it also makes it a bit easier.
One more thing, which might simplify type-handling. I assume you may know this already, so it is just in case. Using enumerations, like this:
TGeoJsonType = (gjtUnknown, gjtPoint, gjtMultiPoint, gjtLineString, gjtMultiLineString, gjtPolygon, gjtMultiPolygon,
gjtGeometryCollection, gjtFeature, gjtFeatureCollection, gjtName, gjtLink);
And, for example:
TGeometry = packed record
coordinates: RawJSON;
&type: TGeoJsonType;
public
function GetLineString(out AResult: TPoint2DList): Boolean;
function GetMultiPolygon(out AResult: TMultiPolygon): Boolean;
....
end;
This option will trim all textual enumerations "gjtLineString" -> "LineString":
TJSONSerializer.SetDefaultEnumTrim(True);
Enumerations will allow to simplify (and speedup) types checking: &type=gjtLineString instead of &type='LineString'. And "case &type of ..." might also make things easier in some cases.
LCoord.lng := GetNextItemDouble(P); LCoord.lat := GetNextItemDouble(P, ']'); // this works.
Noted for the future, thanks!
Offline
Pages: 1