Delphi 2007 JSON array of string to TStringList - json

Embarcadero introduced JSON types with Delphi 2009, but I'm using Delphi 2007 without these JSON types.
I'm wanting to process a JSON string being an "array of strings" into a TStringList object.
e.g.
["Ford", "BMW", "Fiat"]
I guess it's string token type of processing ..

There is a third party Delphi unit that might be useful for this:
https://sourceforge.net/projects/lkjson/
uses uLkJSON;
var
AString:String;
AStringList:TStringList;
jsonbase,Items : TlkJSONbase; //as per 3rd party unit uLkJSON
I: Integer;
begin
AStringList:=TStringList.create;
AString := '["Ford", "BMW", "Fiat"]';
jsonbase := TlkJSON.ParseText(AString);
if Assigned(jsonbase) then
begin
for I := 0 to Pred(jsonbase.Count) do
begin
Items := jsonbase.Child[I];
AStringList.add(Items.Value);
end;
end;
Result := AStringList;
end;

Related

Delphi, TSuperObject > from JSON to Object

I have in Delphi XE4, TSuperObject:
TAspTransactionBasicData = Class(TObject)
Currency : Byte;
Amount : Currency;
constructor Create(aCurrency: Byte; aAmount: Currency);
end;
TStartWorkflowWithBasicData = Class(TObject)
AdditionalData : TAspTransactionBasicData; // here it is as an Object
TypeOfWorkflow : Byte;
constructor Create(aAdditionalData: TAspTransactionBasicData; aTypeOfWorkflow: Byte);
function toJSon(TObject:TStartWorkflowWithBasicData):String;
end;
and the ToJSon function puts the object into JSON:
function TStartWorkflowWithBasicData.toJSon(TObject:TStartWorkflowWithBasicData):String;
Var
JSon: ISuperObject;
RttiCont: TSuperRttiContext;
begin
Result := '';
RttiCont := TSuperRttiContext.Create;
JSon := RttiCont.AsJson<TStartWorkflowWithBasicData>(TObject); // Insert the object into JSON
Result := JSon.AsJSon(False);
RttiCont.Free;
end;
and I would also need the exact opposite, a function fromJSon, which will read the JSON (where the object was inserted) back into the object, but I'm groping...
Can you please advise me?
I can't help you with SuperObject, but kbmMW contains a very complete XML, JSON, YAML, BSON, MessagePack + CSV and in next release also TXT (fixed format) serializer/deserializer and object marshaller/unmarshaller.
It will easily convert both ways, and even between formats.
kbmMW Community Edition contains all (except the new TXT format), and is free, even for commercial use (limited by license).
https://components4developers.blog/2019/03/11/rest-easy-with-kbmmw-24-xml_json_yaml_to_object_conversion/

Why does TJSONObject.AddPair results Self?

I've noticed that TJSONObject.AddPair functions results Self instead of the newly created object:
For example, in System.JSON unit I see the following code:
function TJSONObject.AddPair(const Str: string; const Val: string): TJSONObject;
begin
if (not Str.IsEmpty) and (not Val.IsEmpty) then
AddPair(TJSONPair.Create(Str, Val));
Result := Self;
end;
I was expecting something like that:
function TJSONObject.AddPair(const Str: string; const Val: string): TJSONObject;
begin
if (not Str.IsEmpty) and (not Val.IsEmpty) then
Result := AddPair(TJSONPair.Create(Str, Val));
else
Result := nil;
end;
I find this very unusual, is it a Delphi XE7 bug or is there any technical/practical reason why they did that?
Returning Self is common coding pattern called fluent interface.
It allows you to continue with calls to the same object, creating chain of methods without the need to reference object variable for every call. That makes code more readable, on the other hand it is harder to debug.
var
Obj: TJSONObject;
begin
Obj := TJSONObject.Create
.AddPair('first', 'abc')
.AddPair('second', '123')
.AddPair('third', 'aaa');
...
end;
would be equivalent of
var
Obj: TJSONObject;
begin
Obj := TJSONObject.Create;
Obj.AddPair('first', 'abc');
Obj.AddPair('second', '123');
Obj.AddPair('third', 'aaa');
...
end;
And the generated JSON object will look like:
{
"first": "abc",
"second": "123",
"third": "aaa"
}
That kind of coding style is more prevalent in languages with automatic memory management, because you don't need to introduce intermediate variables.
For instance, if you need JSON string you would use following construct:
var
s: string;
begin
s := TJSONObject.Create
.AddPair('first', 'abc')
.AddPair('second', '123')
.AddPair('third', 'aaa')
.Format(2);
...
end;
The problem with the above code in Delphi is that it creates memory leak as you don't have ability to release intermediate object. Because of that, it is more common to use fluent interface pattern in combination with reference counted classes where automatic memory management will handle releasing of any intermediary object instances.

JSON to XML using XSLT 3.0 - how to load JSON source and call json-to-xml function?

I want to experiment (in Delphi code) with the XSLT 3.0 and its json-to-xml() function:
In XSLT 3.0, an inbound document can be in JSON, rather than XML. The processor can take that document, use the json-to-xml() function to convert it into a specific known XML format, process that through the templates, then convert the resulting output back into JSON (or can convert it into HTML 5 among other formats
But I'm stuck in two places:
How do I use a JSON string as the source for the transform? Trying to loading it into a TXMLDocument gives me (of course?) 'malformed' errors
How would I then apply the "json-to-xml() function". All the examples I find about using XSLT transforms in Delphi use the TransformNode function, as the below code.
Things like lDoc.Node.json-to-xml do not compile.
.
var
lDoc, lXSL, lRes: IXMLDocument;
lUTF8Str : UTF8String;
begin
lDoc := LoadXMLData(AXMLString);
lXSL := LoadXMLData(cRemoveNSTransform);
lRes := NewXMLDocument;
lDoc.Node.TransformNode(lXSL.Node,lRes); // Param types IXMLNode, IXMLDocument
lRes.SaveToXML(lUTF8Str);
Can anyone point me in the right direction?
I'm going to write a 'guide' to my own question that does not use XSLT but instead uses the IP*Works! Delphi components that we have a subscription for.
This may at least give others an available option, or a rough idea how to 'roll your own'.
We use the IP*Works! TipwJSON and TipwXML components.
The trick is to intercept the parsing of the JSON component and then write the detected data to the XML component.
This is code from a test app showing how we did it (I have left logging code in):
TJSONTOXML = class(TIpwJSON)
private
FXML : TipwXML;
FLogLevel : Integer;
procedure ShowLogLine(AMsg: String);
procedure InterceptJSONStartElement(Sender: TObject; const Element: string);
procedure InterceptJSONEndElement(Sender: TObject; const Element: string);
procedure InterceptCharacters(Sender: TObject; const Text: string);
function GetXML: String;
public
property XML: String read GetXML;
constructor Create(AOwner: TForm; ALogLevel: Integer); overload; // For now testing on a Form
end;
constructor TJSONTOXML.Create(AOwner: TForm; ALogLevel: Integer);
begin
inherited Create(AOwner);
FLogLevel := ALogLevel;
Self.BuildDOM := false;
Self.OnStartElement := InterceptJSONStartElement;
Self.OnEndElement := InterceptJSONEndElement;
Self.OnCharacters := InterceptCharacters;
FXML := TipwXML.Create(nil);
end;
procedure TJSONTOXML.InterceptJSONEndElement(Sender: TObject; const Element: string);
begin
if Element = '' then // End of array
begin
if FLogLevel > 2 then ShowLogLine('JSON parse EndElement - Array');
FXML.EndElement;
end
else
begin
if FLogLevel > 2 then ShowLogLine('JSON parse EndElement - Element: ' + Element);
FXML.EndElement;
end;
end;
procedure TJSONTOXML.InterceptJSONStartElement(Sender: TObject; const Element: string);
begin
if Element = '' then // Start of array
begin
if FLogLevel > 2 then ShowLogLine('JSON parse StartElement - Array');
FXML.StartElement('ARRAY','');
end
else
begin
if FLogLevel > 2 then ShowLogLine('JSON parse StartElement - Element: ' + Element);
FXML.StartElement(Uppercase(Element),'');
end;
end;
procedure TJSONTOXML.ShowLogLine(AMsg: String);
// Use WM_COPYDATA to send log info to form
var CopyDataStruct: TCopyDataStruct;
begin
CopyDataStruct.dwData := 0;
CopyDataStruct.cbData := 2 + 2 * Length(AMsg);
CopyDataStruct.lpData := PChar(AMsg);
SendMessage((Owner as TForm).Handle, WM_COPYDATA, (Owner as TForm).Handle, lParam(#CopyDataStruct));
end;
function TJSONTOXML.GetXML: String;
begin
FXML.EndElement;
Result := FXML.OutputData;
end;
procedure TJSONTOXML.InterceptCharacters(Sender: TObject; const Text: string);
var lText: String;
begin
// Always surrounded by quotes, remove:
lText := StripQuotes(Text);
if FLogLevel > 2 then ShowLogLine('JSON parse characters: ' + lText);
FXML.PutString(lText);
end;
With this you can
lJSONToXML := TJSONTOXML.Create(Self,FDataLogLvl);
// Get your JSON data from somewhere, e.g. a HTTP component. Then:
lJSONToXML.Inputdata := lData;
lJSONToXML.Parse; // The Parse method initiates the parsing that was postponed by setting BuildDom := false
// The XML is now in the OutputData property of the TipwXML and can e.g. be retrieved by our:
lOutputData := lJSONToXML.XML;
Note that:
There is no namespace information in the XML
The JSON arrays when converted to XML are converted to nodes named ARRAY
All data is kept in memory

Json Parse "Invalid class typecast" issue. [Delphi XE7]

I am trying to parse a JSON result from the Twitter API using Delphi XE7. I am getting an "Invalid class typecast" error, but I check the JSON with an online verifier and it is OK.
Here is the JSON result:
[
{
"trends":
[
{
"name":"#OneDirectionIsOverParty",
"url":"http://twitter.com/search?q=%23OneDirectionIsOverParty",
"promoted_content":null,
"query":"%23OneDirectionIsOverParty",
"tweet_volume":410022
},
{
"name":"#TheDarkKnight",
"url":"http://twitter.com/search?q=%23TheDarkKnight",
"promoted_content":null,
"query":"%23TheDarkKnight",
"tweet_volume":null
},
{
"name":"#QuintaComOClubeSdv",
"url":"http://twitter.com/search?q=%23QuintaComOClubeSdv",
"promoted_content":null,
"query":"%23QuintaComOClubeSdv",
"tweet_volume":23756
}
],
"as_of":"2016-07-21T20:14:13Z",
"created_at":"2016-07-21T20:08:31Z",
"locations":
[
{
"name":"Worldwide",
"woeid":1
}
]
}
]
This is my parsing function:
procedure ParseJSON(const JSON: string);
var
JSONObject: TJSONObject;
MessageText: TJSONArray;
NodeDetails: TJSONObject;
MsgDetail: TJSONString;
I: Integer;
Item: TListItem;
begin
JSONObject := TJSONObject.ParseJSONValue(TEncoding.UTF8.GetBytes(JSON), 0) as TJSONObject;
MessageText := JSONObject.Get('trends').JSONValue as TJSONArray;
for I := 0 to TJSONArray(MessageText).Size - 1 do
begin
Item := Form1.ListView1.Items.Add;
NodeDetails := MessageText.Get(I) as TJSONObject;
MsgDetail := NodeDetails.Get('query').JSONValue as TJSONString;
Item.Caption := MsgDetail.Value;
end;
Actually, this function works with other JSON results from the Twitter API. It is not working on this one result only.
JSONObject := TJSONObject.ParseJSONValue(TEncoding.UTF8.GetBytes(JSON), 0) as TJSONObject;
The root of the JSON is an array, not an object. Hence the error.
You need to cast the return value of ParseJSONValue() to TJSONArray instead of TJSONObject, and then you can access the first element in the array and read its trends value. You already have code for parsing arrays, so you clearly know how to do that.
If you are not clear on the JSON terminology of object and array, please read the JSON spec.
As David has pointed out, the issue is that your code assumes that the JSON text is an object where in this specific case it is an array.
In situations where code does not know whether a specific JSON container is an object or an array, my alternative JSON library provides a way to deal with this by providing a TJSONText class specifically for dealing with JSON where you do not necessarily know (or care) whether the JSON involved is an object or an array.
In your case the resulting code would be something like:
response := TJSONText.CreateFromUTF8(JSON);
case response.ValueType of
jsArray : MessageText := response.AsArray[0].AsObject['trends'].AsArray;
jsObject : MessageText := NIL; // or as appropriate to extract "trends" from a single object response
end;
if Assigned(MessageText) then
begin
.. etc etc
end;

Custom marshaling TDictionary in Delphi

I need to custom marshal/unmarchal a TDictionary in Delphi (XE). The dictionary is declared as:
TMyRecord = record
key11: integer;
key12: string;
...
end;
TMyDict: TDictionary<string, TMyRecord>;
Now, if i marshal the dictionary without registering a custom converter, the marshaller will put all kind of fields in the JSON string - FOnValueNotify, FKeyCollection, FItems, etc.
What i need is some sort of associative array of associative arrays, i.e.
{"key1":{"key11":"val1","key12":"val2"},"key2":{"key11":"val3","key12":"val4"}}
Unfortunately, i don't know how to write the custom converter and reverter. I'm using Delphi XE and the built in TJSONMarshal and TJSONUnMarshal.
Note: The use of TDictionary for this task is not required. I just cant come with something better.
For a simple case like yours, I tend to use a custom method to represent my object in JSON. But, if you want to create reverter and converter, you should read this article:
http://www.danieleteti.it/?p=146
Another option is TSuperObject which has the ability to marshal to/from JSON using RTTI:
type
TData = record
str: string;
int: Integer;
bool: Boolean;
flt: Double;
end;
var
ctx: TSuperRttiContext;
data: TData;
obj: ISuperObject;
begin
ctx := TSuperRttiContext.Create;
try
data := ctx.AsType<TData>(SO('{str: "foo", int: 123, bool: true, flt: 1.23}'));
obj := ctx.AsJson<TData>(data);
finally
ctx.Free;
end;
end;