xtool/contrib/mORMot/SQLite3/Samples/ThirdPartyDemos/EMartin/SynJSONTreeview/SynJSONTreeView.pas

904 lines
28 KiB
ObjectPascal

/// JSON tree view bases on Synopse JSON parser
// - based on JSON TreeView VCL Component from pawel.glowacki@embarcadero.com
// - this unit is a part of the freeware Synopse mORMot framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SynJSONTreeView;
{
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2022 Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is Synopse mORMot framework.
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (C) 2022
the Initial Developer. All Rights Reserved.
Contributor(s):
- Esteban Martin (EMartin)
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****
Version 1.18
- first public release, corresponding to Synopse mORMot Framework 1.18
TODO:
- FPC support (I just use Windows)
- Developed with Delphi 7, not tested with other Delphi versions
}
{$I Synopse.inc} // define HASINLINE CPU32 CPU64 OWNNORMTOUPPER
interface
uses
SysUtils,
Classes,
ComCtrls,
Controls,
SynCommons,
SynTable;
type
ESynJSONTreeView = class(ESynException);
TSynJSONTreeNode = class;
/// Tree node types
// - jtnObject: JSON object, i.e: {"fieldNumber":123,"fieldText":"text","fieldNull":null}
// - jtnObjectContent: conten of jtnObject, i.e: "fieldNumber":123
// - jtnArray: JSON array, i.e: [123,"text",{"fieldNumber":123,"fieldText":"text","fieldNull":null},null]
// - jtnArrayContent: content of jtnArray, i.e: 123 or {"fieldNumber":123,"fieldText":"text","fieldNull":null}
TSynJSONTreeNodeType = (jtnObject, jtnObjectContent, jtnArray, jtnArrayContent);
/// Event type for fire event when editing
TSynJSONTreeViewCustomInputEvent = procedure(Sender: TObject; Node: TSynJSONTreeNode;
var Prompt: RawUTF8; var Value: Variant;
var Handled: Boolean) of object;
/// Node extension for handling JSON value in the treeview
TSynJSONTreeNode = class(TTreeNode)
protected
fName: RawUTF8;
fJSONType: TSynJSONTreeNodeType;
fValue: Variant;
public
/// Clone the node
function Clone: TSynJSONTreeNode;
/// JSON type in the node
property JSONType: TSynJSONTreeNodeType read fJSONType write fJSONType;
/// Name of the JSON value
property Name: RawUTF8 read FName write fName;
/// JSON value
property Value: Variant read fValue write fValue;
end;
/// Allowed edition states of treeview
// - estEdit: allow edit a node
// - estDelete: allow delete a node
// - estInsert: allow insert a node
TSynJSONTreeViewEditionStateType = (estEdit , estDelete, estInsert);
TSynJSONTreeViewEditionStateSet = set of TSynJSONTreeViewEditionStateType;
/// TSynJSONTreeView is a treeview supporting mORMOn JSON document (TDocVariantData), the allowed tasks are:
// - edit a node
// - delete a node
// - add a node
// - save to file
// - load from file
// - generate JSON to string variable
// - implement a custom input for edit/insert node
TSynJSONTreeView = class(TTreeView)
private
fAllowedEditionStates: TSynJSONTreeViewEditionStateSet;
protected
fFirsttime: Boolean;
fJSONDocument: Variant;
fJSONText: RawUTF8;
fKey: Integer;
fOnDblClickBak: TNotifyEvent;
fOnEditingBak: TTVEditingEvent;
fOnKeyDownBak: TKeyEvent;
fOnCustomInput: TSynJSONTreeViewCustomInputEvent;
fVisibleChildrenCounts: boolean;
fVisibleByteSizes: boolean;
/// Add child node from JSON value
// - aNode: node from which add the child
// - aJSON: JSON value single or object/array
// - aNameOrIndex: if it is numeric, then is the index into the object/array, otherwise the name of the container node
function AddChildFromJSON(const aNode: TSynJSONTreeNode; const aJSON: Variant; const aNameOrIndex: Variant): TSynJSONTreeNode;
procedure BackupEvents;
procedure DeleteNode(Node: TSynJSONTreeNode; const aAsk: Boolean = True);
procedure DoOnCreateNodeClass(Sender: TCustomTreeView; var NodeClass: TTreeNodeClass);
procedure DoOnDblClick(Sender: TObject);
procedure DoOnEditing(Sender: TObject; Node: TTreeNode; var AllowEdit: Boolean);
procedure DoOnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure EditNode(Node: TSynJSONTreeNode);
procedure InsertNode(Node: TSynJSONTreeNode; const aJSON: Variant);
function IsActive: Boolean;
function IsSimpleJsonValue(v: Variant): boolean;{$ifdef HASINLINE}inline;{$endif}
procedure ProcessArray(const aCurrNode: TSynJSONTreeNode; const aValue: Variant; const aNameOrIndex: Variant);
procedure ProcessElement(currNode: TSynJSONTreeNode; arr: Variant; aIndex: integer);
function ProcessJSONText: boolean;
procedure ProcessObject(const aCurrNode: TSynJSONTreeNode; const aValue: Variant; const aNameOrIndex: Variant);
function ProcessPair(currNode: TSynJSONTreeNode; obj: Variant; aIndex: integer): TSynJSONTreeNode;
/// Assign and process the JSON text
// - if Value is null then clear the JSON text and the treeview content
procedure SetJSONText(const Value: RawUTF8);
/// Assign mORMot JSON Document
// - if Value is not equal to the previous value then load the JSON into the treeview
procedure SetSynJSONDocument(const Value: Variant);
/// Show how many children has a container node
procedure SetVisibleChildrenCounts(const Value: boolean);
/// Show byte size of a container node
procedure SetVisibleByteSizes(const Value: boolean);
/// Update info of a container node
procedure UpdateNodeInfo(aNode: TSynJSONTreeNode);
/// Get the original value from variant value
function VariantToRaw(const aVarData: Variant): Variant;
/// Assign source variant to dest variant but considering the dest data type
procedure VariantToVariant(aSource: Variant; var aDest: Variant);
/// Convert variant to UTF8 but checking the boolean type and convert it to true/false string
function VarToUTF8(const aVarData: Variant): RawUTF8;
public
constructor Create(AOwner: TComponent); override;
/// Remove items and JSON text
procedure ClearAll;
/// Load from file
procedure LoadFromFile(const aFileName: TFileName);
/// Load the JSONText in the treeview, clear if empty
procedure LoadJSON;
/// Save to file
procedure SaveToFile(const aFileName: TFileName);
/// Generate JSON from the treeview
function ToJSON: RawUTF8;
/// JSON DocVariantData
property JSONDocument: Variant read FJSONDocument write SeTSynJSONDocument;
published
/// Allowed state editions
property AllowedEditionStates: TSynJSONTreeViewEditionStateSet read fAllowedEditionStates write fAllowedEditionStates;
/// Content the JSON text, when assigned parse the the JSON and load in the treeview
property JSONText: RawUTF8 read fJSONText write SetJSONText;
/// Display info over how many children has each node
property VisibleChildrenCounts: boolean read FVisibleChildrenCounts write SetVisibleChildrenCounts;
/// Display info over the size of node
property VisibleByteSizes: boolean read FVisibleByteSizes write SetVisibleByteSizes;
/// Event fired when editing for custom input
property OnCustomInput: TSynJSONTreeViewCustomInputEvent read fOnCustomInput write fOnCustomInput;
end;
implementation
uses
Windows,
Variants,
mORMotUILogin;
resourcestring
SInvalidJSONValue = 'Invalid JSON value';
SInvalidJSON = 'Invalid JSON: %';
SEnterValue = 'Enter Value';
SInsertNewItem = 'Insert new item';
SInsertNewItemPrompt = 'Enter JSON:\n\n object: <name>:<value>\n array: [<index>]:<value>\n\n ' +
'In array the brackets are required.';
SRemoveChildren = 'This node has children, are you sure to remove them';
SDeleteNode = 'Delete node\n\n% ?';
{ TSynJSONTreeView }
procedure TSynJSONTreeView.ClearAll;
begin
SetJSONText('');
Items.Clear;
end;
constructor TSynJSONTreeView.Create(AOwner: TComponent);
begin
inherited;
OnCreateNodeClass := DoOnCreateNodeClass;
fAllowedEditionStates := [estEdit, estDelete, estInsert];
fFirstTime := True;
FVisibleChildrenCounts := true;
FVisibleByteSizes := false;
end;
procedure TSynJSONTreeView.LoadJson;
var
v: Variant;
begin
BackupEvents;
Items.Clear;
if IsActive then
begin
v := fJSONDocument;
Items.Clear;
if (v._Kind = dvObject) then
ProcessObject(nil, v, Unassigned)
else if (v._Kind = dvArray) then
ProcessArray(nil, v, Unassigned)
else
raise ESynJSONTreeView.Create(SInvalidJSONValue);
FullExpand;
end;
end;
function TSynJSONTreeView.ProcessPair(currNode: TSynJSONTreeNode; obj: Variant; aIndex: integer): TSynJSONTreeNode;
var
p: Variant;
begin
Result := nil;
p := obj._(aIndex);
if IsSimpleJsonValue(p) then
begin
AddChildFromJSON(currNode, obj, aIndex);
Exit;
end;
if (p._Kind = dvObject) then
ProcessObject(currNode, _CopyFast(p), obj.Name(aIndex))
else if (p._Kind = dvArray) then
ProcessArray(currNode, _CopyFast(p), obj.Name(aIndex))
else
raise ESynJSONTreeView.Create(SInvalidJSONValue);
end;
procedure TSynJSONTreeView.ProcessElement(currNode: TSynJSONTreeNode; arr: Variant; aIndex: integer);
var
v: Variant;
begin
v := arr._(aIndex);
if IsSimpleJsonValue(v) then
begin
AddChildFromJSON(currNode, arr, aIndex);
Exit;
end;
if (v._Kind = dvObject) then
ProcessObject(currNode, _CopyFast(v), IntToStr(aIndex))
else if (v._Kind = dvArray) then
ProcessArray(currNode, _CopyFast(v), aIndex)
else
raise ESynJSONTreeView.Create(SInvalidJSONValue);
end;
procedure TSynJSONTreeView.SetSynJSONDocument(const Value: Variant);
begin
if fJSONDocument <> Value then
begin
fJSONDocument := Value;
ClearAll;
if IsActive then
LoadJson;
end;
end;
procedure TSynJSONTreeView.SetVisibleByteSizes(const Value: boolean);
begin
if FVisibleByteSizes <> Value then
begin
FVisibleByteSizes := Value;
LoadJson;
end;
end;
procedure TSynJSONTreeView.SetVisibleChildrenCounts(const Value: boolean);
begin
if FVisibleChildrenCounts <> Value then
begin
FVisibleChildrenCounts := Value;
LoadJson;
end;
end;
function TSynJSONTreeView.VarToUTF8(const aVarData: Variant): RawUTF8;
begin
if (TVarData(aVarData).VType = varBoolean) then
begin
if TVarData(aVarData).VBoolean then
Result := 'true'
else
Result := 'false';
end else
Result := VariantToUTF8(aVarData);
end;
function TSynJSONTreeView.ToJSON: RawUTF8;
var
lW: TTextWriter;
procedure ProcessNode(aNode: TSynJSONTreeNode);
procedure CheckLastComma(aW: TTextWriter);
begin
if (aW.TextLength > 0) and (not (aW.LastChar in [',','[','{'])) then
aW.Add(',');
end;
var
lNode: TSynJSONTreeNode;
begin
lNode := aNode;
repeat
if Assigned(lNode) then begin
case lNode.JSONType of
jtnObject:
begin
CheckLastComma(lW);
// numeric name is excluded
if (lNode.Name <> '') and (StrToIntDef(lNode.Name, MaxInt) = MaxInt) then
lW.AddFieldName(lNode.Name);
lW.Add('{');
end;
jtnArray:
begin
CheckLastComma(lW);
if (lNode.Name <> '') then
lW.AddFieldName(lNode.Name);
lW.Add('[');
end;
jtnObjectContent:
begin
CheckLastComma(lW);
lW.AddFieldName(lNode.Name);
lW.AddVariant(lNode.Value);
lW.Add(',');
end;
jtnArrayContent:
begin
CheckLastComma(lW);
lW.AddVariant(lNode.Value);
lW.Add(',');
end;
end;
end;
if lNode.HasChildren then begin
ProcessNode(TSynJSONTreeNode(lNode.GetFirstChild));
lW.CancelLastComma;
case lNode.JSONType of
jtnObject: lW.Add('}');
jtnArray: lW.Add(']');
end;
end;
lNode := TSynJSONTreeNode(lNode.GetNextSibling);
until not Assigned(lNode);
end;
begin
lW := TTextWriter.CreateOwnedStream;
try
ProcessNode(TSynJSONTreeNode(Items.GetFirstNode));
lW.CancelLastComma;
lW.SetText(Result);
finally
lW.Free;
end;
end;
procedure TSynJSONTreeView.DoOnCreateNodeClass(Sender: TCustomTreeView; var NodeClass: TTreeNodeClass);
begin
NodeClass := TSynJSONTreeNode;
end;
function TSynJSONTreeView.VariantToRaw(const aVarData: Variant): Variant;
var
lVar: TSQLVar;
lTmp: RawByteString;
begin
if VarIs(aVarData, [varBoolean]) then
begin
Result := aVarData;
Exit;
end;
VariantToSQLVar(aVarData, lTmp, lVar);
with lVar do
case VType of
ftNull: Result := null;
ftCurrency: Result := VInt64;
ftUTF8:
if Assigned(VText) then
Result := RawUTF8(PUTF8Char(VText))
else
Result := '';
ftInt64: Result := VInt64;
ftDouble: Result := VDouble;
end;
end;
procedure TSynJSONTreeView.DoOnDblClick(Sender: TObject);
begin
if ReadOnly or (Selected.Count > 0) or (not (estEdit in fAllowedEditionStates)) then
Exit;
EditNode(TSynJSONTreeNode(Selected));
if Assigned(fOnDblClickBak) then
fOnDblClickBak(Sender);
end;
function TSynJSONTreeView.IsActive: Boolean;
begin
Result := (fJSONDocument <> Unassigned) and (not VarIsEmptyOrNull(fJSONDocument)) and (fJSONDocument._Kind <> dvUndefined);
end;
function TSynJSONTreeView.IsSimpleJsonValue(v: Variant): boolean;
begin
Result := VarIsEmptyOrNull(v) or VarIsNumeric(v) or VarIsStr(v) or VarIsStr(v);
end;
procedure TSynJSONTreeView.SetJSONText(const Value: RawUTF8);
begin
if fJSONText <> Value then
begin
fJSONText := Value;
if fJSONText <> '' then
ProcessJSONText
else
begin
TDocVariantData(fJSONDocument).Clear;
fJSONDocument := Unassigned;
end;
end;
end;
function TSynJSONTreeView.ProcessJSONText: boolean;
begin
if IsActive then
fJSONDocument.Clear;
TDocVariantData(fJSONDocument).InitJSON(fJSONText);
if (TDocVariantData(fJSONDocument).Kind = dvUndefined) then
raise ESynJSONTreeView.CreateUTF8(SInvalidJSON, [fJSONText]);
Result := IsActive;
end;
procedure TSynJSONTreeView.BackupEvents;
begin
if not fFirstTime then
Exit;
if not Assigned(fOnDblClickBak) and Assigned(OnDblClick) then
fOnDblClickBak := OnDblClick;
OnDblClick := DoOnDblClick;
if not Assigned(fOnEditingBak) and Assigned(OnEditing) then
fOnEditingBak := OnEditing;
OnEditing := DoOnEditing;
if not Assigned(fOnKeyDownBak) and Assigned(OnKeyDown) then
fOnKeyDownBak := OnKeyDown;
OnKeyDown := DoOnKeyDown;
fFirstTime := False;
end;
procedure TSynJSONTreeView.DoOnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if ReadOnly or (not (Key in [VK_DELETE, VK_INSERT, VK_RETURN])) or
((Key = VK_DELETE) and (not (estDelete in fAllowedEditionStates))) or
((Key = VK_RETURN) and (not (estEdit in fAllowedEditionStates))) or
((Key = VK_INSERT) and (not (estInsert in fAllowedEditionStates))) then
Exit;
case Key of
VK_DELETE: DeleteNode(TSynJSONTreeNode(Selected));
VK_INSERT: InsertNode(TSynJSONTreeNode(Selected), Unassigned);
VK_RETURN: EditNode(TSynJSONTreeNode(Selected));
end;
if Assigned(fOnKeyDownBak) then
fOnKeyDownBak(Sender, Key, Shift);
end;
procedure TSynJSONTreeView.UpdateNodeInfo(aNode: TSynJSONTreeNode);
procedure UpdateNodeText(aNode: TSynJSONTreeNode; const aText: RawUTF8);
begin
if not VarIsNull(aNode.Value) then
aNode.Text := aText
else
aNode.Text := aNode.Name + ': null';
end;
begin
case aNode.JSONType of
jtnObject: begin
if (not Assigned(aNode.Parent)) or (TSynJSONTreeNode(aNode.Parent).JSONType <> jtnArray) then
UpdateNodeText(aNode, aNode.Name + ' {}')
else
UpdateNodeText(aNode, FormatUTF8('[%] {}', [aNode.Name]));
end;
jtnArray:
if not VarIsNull(aNode.Value) then
UpdateNodeText(aNode, aNode.Name + ' []');
end;
case aNode.JSONType of
jtnObject, jtnArray: begin
if VisibleChildrenCounts then
if not VarIsNull(aNode.Value) then
UpdateNodeText(aNode, aNode.Text + ' (' + IntToStr(aNode.Value._Count) + ')')
else
UpdateNodeText(aNode, '');
if VisibleByteSizes then
if not VarIsNull(aNode.Value) then
UpdateNodeText(aNode, aNode.Text + ' (size: ' + IntToStr(Length(aNode.Value._JSON)) + ' bytes)')
else
UpdateNodeText(aNode, '');
end;
jtnObjectContent, jtnArrayContent: begin
UpdateNodeText(aNode, '');
end;
end;
end;
procedure TSynJSONTreeView.DeleteNode(Node: TSynJSONTreeNode; const aAsk: Boolean);
procedure RecalculateChildNames(aNode: TSynJSONTreeNode);
var
lNode: TSynJSONTreeNode;
I: Integer;
begin
lNode := TSynJSONTreeNode(aNode.GetFirstChild);
if not Assigned(lNode) then
Exit;
I := 0;
case lNode.JSONType of
jtnObject: begin
repeat
if Assigned(lNode) then begin
if (StrToIntDef(lNode.Name, MaxInt) <> MaxInt) then
lNode.Name := IntToStr(I);
UpdateNodeInfo(lNode);
Inc(I);
end;
lNode := TSynJSONTreeNode(lNode.getNextSibling);
until not Assigned(lNode);
end;
jtnArrayContent: begin
repeat
if Assigned(lNode) then begin
lNode.Name := IntToStr(I);
lNode.Text := UTF8ToString(FormatUTF8('[%]: %', [lNode.Name, lNode.Value]));
Inc(I);
end;
lNode := TSynJSONTreeNode(lNode.getNextSibling);
until not Assigned(lNode);
end;
end;
end;
procedure UpdateEmptyParent(aParent: TSynJSONTreeNode);
begin
if (aParent.Count = 0) then begin
aParent.Value := NULL;
case TSynJSONTreeNode(aParent.Parent).JSONType of
jtnObject: aParent.JSONType := jtnObjectContent;
jtnArray: aParent.JSONType := jtnArrayContent;
end;
end;
end;
var
lParent, lDeletedNode: TSynJSONTreeNode;
begin
if aAsk then
if (YesNo(UTF8ToString(FormatUTF8(SDeleteNode, [Node.Text])), '', False) <> mrYes) then
Exit;
lDeletedNode := Node.Clone;
lParent := TSynJSONTreeNode(Node.Parent);
Items.BeginUpdate;
try
Node.Delete;
Node := lDeletedNode;
// if node name is empty it means that the JSON object is empty
if (Node.Name = '') then begin
ClearAll;
Exit;
end;
case Node.JSONType of
jtnArray, jtnObject: begin
if (StrToIntDef(Node.Name, MaxInt) <> MaxInt) then
TDocVariantData(lParent.Value).Delete(StrToInt(Node.Name))
else
TDocVariantData(lParent.Value).Delete(Node.Name);
if (lParent.Count = 0) then
UpdateEmptyParent(lParent)
else
RecalculateChildNames(lParent);
UpdateNodeInfo(lParent);
end;
jtnArrayContent: begin
TDocVariantData(lParent.Value).Delete(StrToInt(Node.Name));
if (lParent.Count = 0) then
UpdateEmptyParent(lParent)
else
RecalculateChildNames(lParent);
UpdateNodeInfo(lParent);
end;
jtnObjectContent: begin
TDocVariantData(lParent.Value).Delete(Node.Name);
if (lParent.Count = 0) then
UpdateEmptyParent(lParent);
UpdateNodeInfo(lParent);
end;
end;
finally
Items.EndUpdate;
end;
end;
procedure TSynJSONTreeView.InsertNode(Node: TSynJSONTreeNode; const aJSON: Variant);
var
lJSON: Variant;
lPrompt: RawUTF8;
lValue: Variant;
I: Integer;
lCount: Integer;
lHandled: Boolean;
begin
if (aJSON = Unassigned) then begin
lPrompt := SInsertNewItemPrompt;
if Assigned(fOnCustomInput) then
fOnCustomInput(Self, Node, lPrompt, lValue, lHandled);
if not lHandled then
lValue := InputBox(SInsertNewItem, lPrompt, '');
if (lValue = '') then
Exit;
if (lValue[1] <> '[') then
if (lValue[1] <> '{') then
lValue := '{' + lValue + '}';
TDocVariantData(lJSON).InitJSON(lValue);
if (TDocVariantData(lJSON).Kind = dvUndefined) then
raise ESynJSONTreeView.CreateUTF8(SInvalidJSON, [lValue]);
end else
lJSON := aJSON;
// always will be an object the inserted node
lCount := lJSON._Count;
TDocVariantData(TSynJSONTreeNode(Node).Value).AddOrUpdateObject(lJSON);
for I := 0 to lCount-1 do begin
lValue := lJSON._(I);
if IsSimpleJsonValue(lValue) then begin
AddChildFromJSON(Node, lJSON, I);
end else
ProcessObject(Node, _CopyFast(lValue), lJSON.Name(I));
end;
UpdateNodeInfo(Node);
end;
function TSynJSONTreeView.AddChildFromJSON(const aNode: TSynJSONTreeNode; const aJSON: Variant; const aNameOrIndex: Variant): TSynJSONTreeNode;
var
lValue: Variant;
begin
Result := TSynJSONTreeNode(Items.AddChild(aNode, ''));
if not VarIsEmpty(aNameOrIndex) and (VariantToIntegerDef(aNameOrIndex, -1) <> -1) then
lValue := VariantToRaw(aJSON._(aNameOrIndex))
else
lValue := aJSON;
if (TDocVariantData(lValue).Kind <> dvUndefined) then
Result.Value := _CopyFast(lValue)
else
Result.Value := lValue;
case TDocVariantData(aJSON).Kind of
dvObject:
if not VarIsEmpty(aNameOrIndex) then begin
// is name or index is numeric, the text property is assigned showing the name and value, otherwise is a
// container node
if (VariantToIntegerDef(aNameOrIndex, -1) <> -1) then begin
Result.Name := aJSON.Name(aNameOrIndex);
Result.JSONType := jtnObjectContent;
Result.Text := Result.Name + ': ' + VarToUTF8(lValue);
end else begin
Result.Name := aNameOrIndex;
Result.JSONType := jtnObject;
end;
end;
dvArray:
if not VarIsEmpty(aNameOrIndex) then begin
// is name or index is numeric, the text property is assigned showing the name and value, otherwise is a
// container node
if (VariantToIntegerDef(aNameOrIndex, -1) <> -1) then begin
Result.Name := aNameOrIndex;
Result.JSONType := jtnArrayContent;
Result.Text := IntToStr(aNameOrIndex) + ': ' + VarToUTF8(lValue);
end else begin
Result.Name := aNameOrIndex;
Result.JSONType := jtnArray;
end;
end;
end;
end;
procedure TSynJSONTreeView.ProcessObject(const aCurrNode: TSynJSONTreeNode; const aValue: Variant; const aNameOrIndex: Variant);
var
lCount: Integer;
I: Integer;
lCurrNode: TSynJSONTreeNode;
begin
lCurrNode := AddChildFromJSON(aCurrNode, aValue, aNameOrIndex);
UpdateNodeInfo(lCurrNode);
lCount := aValue._Count;
for I := 0 to lCount - 1 do
ProcessPair(lCurrNode, _CopyFast(aValue), I);
end;
procedure TSynJSONTreeView.ProcessArray(const aCurrNode: TSynJSONTreeNode; const aValue: Variant; const aNameOrIndex: Variant);
var
lCount: Integer;
I: Integer;
lCurrNode: TSynJSONTreeNode;
begin
lCurrNode := AddChildFromJSON(aCurrNode, aValue, aNameOrIndex);
UpdateNodeInfo(lCurrNode);
lCount := aValue._Count;
for I := 0 to lCount - 1 do
ProcessElement(lCurrNode, aValue, I);
end;
procedure TSynJSONTreeView.EditNode(Node: TSynJSONTreeNode);
var
lPrompt: RawUTF8;
lJSON, lValue, lDestValue: Variant;
lHandled: Boolean;
begin
lPrompt := UTF8ToString(TSynJSONTreeNode(Selected).Name);
if (Selected.Text[1] = '[') then
lPrompt := FormatUTF8('[%]', [lPrompt]);
lValue := '';
if not VarIsNull(TSynJSONTreeNode(Selected).Value) then
lValue := TSynJSONTreeNode(Selected).Value;
if Assigned(fOnCustomInput) then
fOnCustomInput(Self, Node, lPrompt, lValue, lHandled);
if not lHandled then
lValue := InputBox(SEnterValue, lPrompt, lValue);
// if not modification, exit
if (lValue = TSynJSONTreeNode(Selected).Value) then
Exit;
// remove children if input data was null
if (TDocVariantData(lValue).Kind = dvUndefined) and
((lValue = '') or IdemPChar(pointer(VariantToUTF8(lValue)), 'NULL')) and
Selected.HasChildren then begin
if (YesNo(SRemoveChildren, '', False) = mrYes) then begin
Selected.DeleteChildren;
TSynJSONTreeNode(Selected).Value := NULL;
UpdateNodeInfo(TSynJSONTreeNode(Selected));
Exit;
end;
end;
TDocVariantData(lJSON).InitJSON(lValue);
case TDocVariantData(lJSON).Kind of
dvUndefined: begin
Selected.Text := FormatUTF8('%: %', [lPrompt, lValue]);
lDestValue := TSynJSONTreeNode(Selected).Value;
VariantToVariant(lValue, lDestValue);
TSynJSONTreeNode(Selected).Value := lDestValue;
if Assigned(Selected.Parent) then begin
case TSynJSONTreeNode(Selected.Parent).JSONType of
jtnObject: TSynJSONTreeNode(Selected).JSONType := jtnObjectContent;
jtnArray: begin
TSynJSONTreeNode(Selected).JSONType := jtnArrayContent;
TDocVariantData(TSynJSONTreeNode(Selected.Parent).Value).Values[StrToInt(TSynJSONTreeNode(Selected).Name)] :=
TSynJSONTreeNode(Selected).Value;
end;
end;
end;
end;
else
case TDocVariantData(lJSON).Kind of
dvObject: TSynJSONTreeNode(Selected).JSONType := jtnObject;
dvArray: TSynJSONTreeNode(Selected).JSONType := jtnArray;
end;
Node.Value := _CopyFast(lJSON);
if Node.HasChildren then
Node.DeleteChildren;
InsertNode(Node, lJSON);
UpdateNodeInfo(Node);
end;
end;
procedure TSynJSONTreeView.LoadFromFile(const aFileName: TFileName);
begin
if (aFileName = '') then
Exit;
SetJSONText(StringFromFile(aFileName));
LoadJSON;
end;
procedure TSynJSONTreeView.SaveToFile(const aFileName: TFileName);
var
lJSON: RawUTF8;
begin
if (aFileName = '') then
Exit;
lJSON := ToJSON;
FileFromString(lJSON, aFileName);
end;
procedure TSynJSONTreeView.DoOnEditing(Sender: TObject; Node: TTreeNode; var AllowEdit: Boolean);
begin
AllowEdit := (estEdit in fAllowedEditionStates);
if AllowEdit and Assigned(fOnEditingBak) then
fOnEditingBak(Sender, Node, AllowEdit);
end;
procedure TSynJSONTreeView.VariantToVariant(aSource: Variant; var aDest: Variant);
var
lVar: TSQLVar;
lTmp: RawByteString;
begin
VariantToSQLVar(aDest, lTmp, lVar);
with lVar do
case VType of
ftNull: aDest := aSource;
ftCurrency: aDest := aSource.VInt64;
ftUTF8: aDest := VariantToUTF8(aSource);
ftInt64: begin
if (aSource <> '') then
if (UpperCaseU(aSource) <> 'TRUE') and (UpperCaseU(aSource) <> 'FALSE') then begin
aDest := StrToInt64Def(aSource, MaxInt);
if (aDest = MaxInt) then
raise ESynJSONTreeView.CreateUTF8('Invalid input data type: %', [aSource]);
end else
if (UpperCaseU(aSource) = 'TRUE') then
aDest := True
else
aDest := False;
end;
ftDouble: aDest := aSource.VDouble;
end;
end;
{ TSynJSONTreeNode }
function TSynJSONTreeNode.Clone: TSynJSONTreeNode;
begin
Result := TSynJSONTreeNode.Create(nil);
Result.Name := fName;
Result.JSONType := fJSONType;
Result.Value := fValue;
end;
end.