mirror of
https://github.com/danieleteti/delphimvcframework.git
synced 2024-11-15 07:45:54 +01:00
12031 lines
420 KiB
ObjectPascal
12031 lines
420 KiB
ObjectPascal
/// Framework Core Low-Level Variants / TDocVariant process
|
|
// - this unit is a part of the Open Source Synopse mORMot framework 2,
|
|
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
|
|
unit mormot.core.variants;
|
|
|
|
{
|
|
*****************************************************************************
|
|
|
|
Variant / TDocVariant feature shared by all framework units
|
|
- Low-Level Variant Wrappers
|
|
- Custom Variant Types with JSON support
|
|
- TDocVariant Object/Array Document Holder with JSON support
|
|
- IDocList/IDocDict advanced Wrappers of TDocVariant Documents
|
|
- JSON Parsing into Variant
|
|
- Variant Binary Serialization
|
|
|
|
*****************************************************************************
|
|
}
|
|
|
|
interface
|
|
|
|
{$I \mormot.defines.inc}
|
|
|
|
uses
|
|
sysutils,
|
|
classes,
|
|
variants,
|
|
mormot.core.base,
|
|
mormot.core.os,
|
|
mormot.core.unicode,
|
|
mormot.core.text,
|
|
mormot.core.data, // already included in mormot.core.json
|
|
mormot.core.buffers,
|
|
mormot.core.rtti,
|
|
mormot.core.json;
|
|
|
|
|
|
{ ************** Low-Level Variant Wrappers }
|
|
|
|
type
|
|
/// exception class raised by this unit during raw Variant process
|
|
ESynVariant = class(ESynException);
|
|
|
|
const
|
|
{$ifdef HASVARUSTRING}
|
|
varFirstCustom = varUString + 1;
|
|
{$else}
|
|
varFirstCustom = varAny + 1;
|
|
{$endif HASVARUSTRING}
|
|
|
|
/// fastcheck if a variant hold a value
|
|
// - varEmpty, varNull or a '' string would be considered as void
|
|
// - varBoolean=false or varDate=0 would be considered as void
|
|
// - a TDocVariantData with Count=0 would be considered as void
|
|
// - any other value (e.g. floats or integer) would be considered as not void
|
|
function VarIsVoid(const V: Variant): boolean;
|
|
|
|
/// returns a supplied string as variant, or null if v is void ('')
|
|
function VarStringOrNull(const v: RawUtf8): variant;
|
|
|
|
type
|
|
/// a set of simple TVarData.VType, as specified to VarIs()
|
|
TVarDataTypes = set of 0..255;
|
|
|
|
/// allow to check for a specific set of TVarData.VType
|
|
function VarIs(const V: Variant; const VTypes: TVarDataTypes): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// same as Dest := Source, but copying by reference
|
|
// - i.e. VType is defined as varVariant or varByRef / varVariantByRef
|
|
// - for instance, it will be used for late binding of TDocVariant properties,
|
|
// to let following statements work as expected:
|
|
// ! V := _Json('{arr:[1,2]}');
|
|
// ! V.arr.Add(3); // will work, since V.arr will be returned by reference
|
|
// ! writeln(V); // will write '{"arr":[1,2,3]}'
|
|
procedure SetVariantByRef(const Source: Variant; var Dest: Variant);
|
|
|
|
/// same as Dest := Source, but copying by value
|
|
// - will unreference any varByRef content
|
|
// - will convert any string value into RawUtf8 (varString) for consistency
|
|
procedure SetVariantByValue(const Source: Variant; var Dest: Variant);
|
|
|
|
/// same as FillChar(Value^,SizeOf(TVarData),0)
|
|
// - so can be used for TVarData or Variant
|
|
// - it will set V.VType := varEmpty, so Value will be Unassigned
|
|
// - it won't call VarClear(variant(Value)): it should have been cleaned before
|
|
procedure ZeroFill(Value: PVarData);
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// fill all bytes of the value's memory buffer with zeros, i.e. 'toto' -> #0#0#0#0
|
|
// - may be used to cleanup stack-allocated content
|
|
procedure FillZero(var value: variant); overload;
|
|
|
|
/// convert an UTF-8 encoded text buffer into a variant RawUtf8 varString
|
|
// - this overloaded version expects a destination variant type (e.g. varString
|
|
// varOleStr / varUString) - if the type is not handled, will raise an
|
|
// EVariantTypeCastError
|
|
procedure RawUtf8ToVariant(const Txt: RawUtf8; var Value: TVarData;
|
|
ExpectedValueType: cardinal); overload;
|
|
|
|
/// convert an open array (const Args: array of const) argument to a variant
|
|
// - note that, due to a Delphi compiler limitation, cardinal values should be
|
|
// type-casted to Int64() (otherwise the integer mapped value will be converted)
|
|
// - vt*String or vtVariant arguments are returned as varByRef
|
|
procedure VarRecToVariant(const V: TVarRec; var result: variant); overload;
|
|
|
|
/// convert an open array (const Args: array of const) argument to a variant
|
|
// - note that, due to a Delphi compiler limitation, cardinal values should be
|
|
// type-casted to Int64() (otherwise the integer mapped value will be converted)
|
|
// - vt*String or vtVariant arguments are returned as varByRef
|
|
function VarRecToVariant(const V: TVarRec): variant; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// convert a variant to an open array (const Args: array of const) argument
|
|
// - variant is accessed by reference as vtVariant so should remain available
|
|
procedure VariantToVarRec(const V: variant; var result: TVarRec);
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// convert a variant array to open array (const Args: array of const) arguments
|
|
// - variants are accessed by reference as vtVariant so should remain available
|
|
procedure VariantsToArrayOfConst(const V: array of variant; VCount: PtrInt;
|
|
out result: TTVarRecDynArray); overload;
|
|
|
|
/// convert a variant array to open array (const Args: array of const) arguments
|
|
// - variants are accessed by reference as vtVariant so should remain available
|
|
function VariantsToArrayOfConst(const V: array of variant): TTVarRecDynArray; overload;
|
|
|
|
/// convert an array of RawUtf8 to open array (const Args: array of const) arguments
|
|
// - RawUtf8 are accessed by reference as vtAnsiString so should remain available
|
|
function RawUtf8DynArrayToArrayOfConst(const V: array of RawUtf8): TTVarRecDynArray;
|
|
|
|
/// convert any Variant into a RTL string type
|
|
// - expects any varString value to be stored as a RawUtf8
|
|
// - prior to Delphi 2009, use VariantToString(aVariant) instead of
|
|
// string(aVariant) to safely retrieve a string=AnsiString value from a variant
|
|
// generated by our framework units - otherwise, you may loose encoded characters
|
|
// - for Unicode versions of Delphi, there won't be any potential data loss,
|
|
// but this version may be slightly faster than a string(aVariant)
|
|
function VariantToString(const V: Variant): string; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// convert any Variant into a RTL string type
|
|
procedure VariantToString(const V: Variant; var result: string); overload;
|
|
|
|
/// convert a dynamic array of variants into its JSON serialization
|
|
// - will use a TDocVariantData temporary storage
|
|
function VariantDynArrayToJson(const V: TVariantDynArray): RawUtf8;
|
|
|
|
/// convert a dynamic array of variants into its text values
|
|
function VariantDynArrayToRawUtf8DynArray(const V: TVariantDynArray): TRawUtf8DynArray;
|
|
|
|
/// convert a JSON array into a dynamic array of variants
|
|
// - will use a TDocVariantData temporary storage
|
|
function JsonToVariantDynArray(const Json: RawUtf8): TVariantDynArray;
|
|
|
|
/// convert an open array list into a dynamic array of variants
|
|
// - will use a TDocVariantData temporary storage
|
|
function ValuesToVariantDynArray(const items: array of const): TVariantDynArray;
|
|
|
|
type
|
|
/// function prototype used internally for variant comparison
|
|
// - as used e.g. by TDocVariantData.SortByValue
|
|
TVariantCompare = function(const V1, V2: variant): PtrInt;
|
|
/// function prototype used internally for extended variant comparison
|
|
// - as used by TDocVariantData.SortByRow
|
|
TVariantComparer = function(const V1, V2: variant): PtrInt of object;
|
|
/// function prototype used internally for extended variant comparison
|
|
// - as used by TDocVariantData.SortArrayByFields
|
|
TVariantCompareField = function(const FieldName: RawUtf8;
|
|
const V1, V2: variant): PtrInt of object;
|
|
|
|
/// internal function as called by inlined VariantCompare/VariantCompareI and
|
|
// the SortDynArrayVariantComp() function overriden by this unit
|
|
function FastVarDataComp(A, B: PVarData; caseInsensitive: boolean): integer;
|
|
|
|
/// TVariantCompare-compatible case-sensitive comparison function
|
|
// - just a wrapper around FastVarDataComp(caseInsensitive=false)
|
|
function VariantCompare(const V1, V2: variant): PtrInt;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// TVariantCompare-compatible case-insensitive comparison function
|
|
// - just a wrapper around FastVarDataComp(caseInsensitive=true)
|
|
function VariantCompareI(const V1, V2: variant): PtrInt;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// fast comparison of a Variant and UTF-8 encoded String (or number)
|
|
// - slightly faster than plain V=Str, which computes a temporary variant
|
|
// - here Str='' equals unassigned (varEmpty), null or false
|
|
// - if CaseSensitive is false, will use PropNameEquals() for comparison
|
|
function VariantEquals(const V: Variant; const Str: RawUtf8;
|
|
CaseSensitive: boolean = true): boolean; overload;
|
|
|
|
/// return the TVarData.VType as text, e.g. 'Integer', 'String' or 'DocVariant'
|
|
function VariantTypeName(V: PVarData): PShortString; overload;
|
|
|
|
/// return the variant VType as text, e.g. 'Integer', 'String' or 'DocVariant'
|
|
function VariantTypeName(const V: variant): PShortString; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
|
|
{ ************** Custom Variant Types with JSON support }
|
|
|
|
type
|
|
/// define how our custom variant types behave, i.e. its methods featureset
|
|
TSynInvokeableVariantTypeOptions = set of (
|
|
sioHasTryJsonToVariant,
|
|
sioHasToJson,
|
|
sioCanIterate);
|
|
|
|
/// custom variant handler with easier/faster access of variant properties,
|
|
// and JSON serialization support
|
|
// - default GetProperty/SetProperty methods are called via some protected
|
|
// virtual IntGet/IntSet methods, with less overhead (to be overriden)
|
|
// - these kind of custom variants will be faster than the default
|
|
// TInvokeableVariantType for properties getter/setter, but you should
|
|
// manually register each type by calling SynRegisterCustomVariantType()
|
|
// - also feature custom JSON parsing, via TryJsonToVariant() protected method
|
|
TSynInvokeableVariantType = class(TInvokeableVariantType)
|
|
protected
|
|
fOptions: TSynInvokeableVariantTypeOptions;
|
|
{$ifdef ISDELPHI}
|
|
/// our custom call backs do not want the function names to be uppercased
|
|
function FixupIdent(const AText: string): string; override;
|
|
{$endif ISDELPHI}
|
|
// intercept for a faster direct IntGet/IntSet calls
|
|
// - note: SetProperty/GetProperty are never called by this class/method
|
|
// - also circumvent FPC 3.2+ inverted parameters order
|
|
{$ifdef FPC_VARIANTSETVAR}
|
|
procedure DispInvoke(Dest: PVarData; var Source: TVarData;
|
|
CallDesc: PCallDesc; Params: Pointer); override;
|
|
{$else} // see http://mantis.freepascal.org/view.php?id=26773
|
|
{$ifdef ISDELPHIXE7}
|
|
procedure DispInvoke(Dest: PVarData; [ref] const Source: TVarData;
|
|
CallDesc: PCallDesc; Params: Pointer); override;
|
|
{$else}
|
|
procedure DispInvoke(Dest: PVarData; const Source: TVarData;
|
|
CallDesc: PCallDesc; Params: Pointer); override;
|
|
{$endif ISDELPHIXE7}
|
|
{$endif FPC_VARIANTSETVAR}
|
|
public
|
|
/// virtual constructor which should set the custom type Options
|
|
constructor Create; virtual;
|
|
/// search of a registered custom variant type from its low-level VarType
|
|
// - will first compare with its own VarType for efficiency
|
|
// - returns true and set the matching CustomType if found, false otherwise
|
|
function FindSynVariantType(aVarType: cardinal;
|
|
out CustomType: TSynInvokeableVariantType): boolean; overload;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// search of a registered custom variant type from its low-level VarType
|
|
// - will first compare with its own VarType for efficiency
|
|
function FindSynVariantType(aVarType: cardinal): TSynInvokeableVariantType; overload;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// customization of JSON parsing into variants
|
|
// - is enabled only if the sioHasTryJsonToVariant option is set
|
|
// - will be called by e.g. by VariantLoadJson() or GetVariantFromJsonField()
|
|
// with Options: PDocVariantOptions parameter not nil
|
|
// - this default implementation will always returns FALSE,
|
|
// meaning that the supplied JSON is not to be handled by this custom
|
|
// (abstract) variant type
|
|
// - this method could be overridden to identify any custom JSON content
|
|
// and convert it into a dedicated variant instance, then return TRUE
|
|
// - warning: should NOT modify JSON buffer in-place, unless it returns true
|
|
function TryJsonToVariant(var Json: PUtf8Char; var Value: variant;
|
|
EndOfObject: PUtf8Char): boolean; virtual;
|
|
/// customization of variant into JSON serialization
|
|
procedure ToJson(W: TJsonWriter; Value: PVarData); overload; virtual;
|
|
/// save a variant as UTF-8 encoded JSON
|
|
// - implemented as a wrapper around ToJson()
|
|
procedure ToJson(Value: PVarData; var Json: RawUtf8;
|
|
const Prefix: RawUtf8 = ''; const Suffix: RawUtf8 = '';
|
|
Format: TTextWriterJsonFormat = jsonCompact); overload; virtual;
|
|
/// clear the content
|
|
// - this default implementation will set VType := varEmpty
|
|
// - override it if your custom type needs to manage its internal memory
|
|
procedure Clear(var V: TVarData); override;
|
|
/// compare two items - this overriden method will redirect to Compare()
|
|
// - Delphi RTL does this redirection, where FPC does not (but should)
|
|
function CompareOp(const Left, Right: TVarData;
|
|
const Operation: TVarOp): boolean; override;
|
|
/// compare two items - overriden method calling case-sensitive IntCompare()
|
|
procedure Compare(const Left, Right: TVarData;
|
|
var Relationship: TVarCompareResult); override;
|
|
/// copy two variant content
|
|
// - this default implementation will copy the TVarData memory
|
|
// - override it if your custom type needs to manage its internal structure
|
|
procedure Copy(var Dest: TVarData; const Source: TVarData;
|
|
const Indirect: boolean); override;
|
|
/// copy two variant content by value
|
|
// - this default implementation will call the Copy() method
|
|
// - override it if your custom types may use a by reference copy pattern
|
|
procedure CopyByValue(var Dest: TVarData;
|
|
const Source: TVarData); virtual;
|
|
/// this method will allow to look for dotted name spaces, e.g. 'parent.child'
|
|
// - return Unassigned (varEmpty) if the FullName does not match any value
|
|
// - will identify TDocVariant storage, or resolve and call the generic
|
|
// TSynInvokeableVariantType.IntGet() method until nested value match
|
|
// - you can set e.g. PathDelim = '/' to search e.g. for 'parent/child'
|
|
procedure Lookup(var Dest: TVarData; const Instance: TVarData;
|
|
FullName: PUtf8Char; PathDelim: AnsiChar = '.');
|
|
/// will check if the value is an array, and return the number of items
|
|
// - if the document is an array, will return the items count (0 meaning
|
|
// void array) - used e.g. by TSynMustacheContextVariant
|
|
// - this default implementation will return -1 (meaning this is not an array)
|
|
// - overridden method could implement it, e.g. for TDocVariant of kind
|
|
// dvArray - or dvObject (ignoring names) if GetObjectAsValues is true
|
|
function IterateCount(const V: TVarData; GetObjectAsValues: boolean): integer; virtual;
|
|
/// allow to loop over an array document
|
|
// - Index should be in 0..IterateCount-1 range
|
|
// - this default implementation will do nothing
|
|
procedure Iterate(var Dest: TVarData; const V: TVarData;
|
|
Index: integer); virtual;
|
|
/// returns TRUE if the supplied variant is of the exact custom type
|
|
function IsOfType(const V: variant): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// returns TRUE if the supplied custom variant is void
|
|
// - e.g. returns true for a TDocVariant or TBsonVariant with Count = 0
|
|
// - caller should have ensured that it is of the exact custom type
|
|
function IsVoid(const V: TVarData): boolean; virtual;
|
|
/// override this abstract method for actual getter by name implementation
|
|
function IntGet(var Dest: TVarData; const Instance: TVarData;
|
|
Name: PAnsiChar; NameLen: PtrInt; NoException: boolean): boolean; virtual;
|
|
/// override this abstract method for actual setter by name implementation
|
|
function IntSet(const Instance, Value: TVarData;
|
|
Name: PAnsiChar; NameLen: PtrInt): boolean; virtual;
|
|
/// override this method if default VariantCompAsText() call is not optimal
|
|
function IntCompare(const Instance, Another: TVarData;
|
|
CaseInsensitive: boolean): integer; virtual;
|
|
/// identify how this custom type behave
|
|
// - as set by the class constructor, to avoid calling any virtual method
|
|
property Options: TSynInvokeableVariantTypeOptions
|
|
read fOptions;
|
|
end;
|
|
|
|
/// class-reference type (metaclass) of custom variant type definition
|
|
// - used by SynRegisterCustomVariantType() function
|
|
TSynInvokeableVariantTypeClass = class of TSynInvokeableVariantType;
|
|
|
|
var
|
|
/// internal list of our TSynInvokeableVariantType instances
|
|
// - SynVariantTypes[0] is always DocVariantVType
|
|
// - SynVariantTypes[1] is e.g. BsonVariantType from mormot.db.nosql.bson
|
|
// - instances are owned by Variants.pas as TInvokeableVariantType instances
|
|
// - is defined here for proper FindSynVariantType inlining
|
|
SynVariantTypes: array of TSynInvokeableVariantType;
|
|
|
|
/// register a custom variant type to handle properties
|
|
// - the registration process is thread-safe
|
|
// - this will implement an internal mechanism used to bypass the default
|
|
// _DispInvoke() implementation in Variant.pas, to use a faster version
|
|
// - is called in case of TDocVariant, TBsonVariant or TSqlDBRowVariant
|
|
function SynRegisterCustomVariantType(
|
|
aClass: TSynInvokeableVariantTypeClass): TSynInvokeableVariantType;
|
|
|
|
/// search of a registered custom variant type from its low-level VarType
|
|
// - returns the matching custom variant type, nil if not found
|
|
function FindSynVariantType(aVarType: cardinal): TSynInvokeableVariantType;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// try to serialize a custom variant value into JSON
|
|
// - as used e.g. by TJsonWriter.AddVariant
|
|
function CustomVariantToJson(W: TJsonWriter; Value: PVarData;
|
|
Escape: TTextWriterKind): boolean;
|
|
|
|
/// low-level conversion of a Compare() result to TCustomVariantType.Compare
|
|
function SortCompTo(cmp: integer): TVarCompareResult;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
|
|
{ ************** TDocVariant Object/Array Document Holder with JSON support }
|
|
|
|
type
|
|
/// JSON_[] constant convenient TDocVariant options
|
|
// - mVoid defines a safe (and slow) full-copy behavior with [] (no option)
|
|
// - mDefault defines a safe (and slow) full-copy behavior, returning null
|
|
// for unknown fields, as defined e.g. by _Json() and _JsonFmt() functions
|
|
// or JSON_OPTIONS[false]
|
|
// - mFast will copy-by-reference any TDocVariantData content, as defined
|
|
// e.g. by _JsonFast() and _JsonFastFmt() functions or JSON_OPTIONS[true]
|
|
// - mFastFloat will copy-by-reference and can parse floating points as double
|
|
// - mFastStrict will copy-by-reference and only parse strict (quoted) JSON,
|
|
// as defined by JSON_FAST_STRICT global variable
|
|
// - mFastExtended will copy-by-reference and write extended (unquoted) JSON,
|
|
// as defined by JSON_FAST_EXTENDED global variable
|
|
// - mFastExtendedIntern will copy-by-reference, write extended JSON and
|
|
// intern names and values, as defined by JSON_FAST_EXTENDEDINTERN variable
|
|
// - mNameValue will copy-by-reference and check field names case-sensitively,
|
|
// as defined by JSON_NAMEVALUE[false] global variable
|
|
// - mNameValueExtended will copy-by-reference, check field names
|
|
// case-sensitively and write extended (unquoted) JSON,
|
|
// as defined by JSON_NAMEVALUE[true] global variable
|
|
// - mNameValueIntern will copy-by-reference, check field names
|
|
// case-sensitively and intern names and values,
|
|
// as defined by JSON_NAMEVALUEINTERN[false] global variable
|
|
// - mNameValueInternExtended will copy-by-reference, check field names
|
|
// case-sensitively, write extended JSON and intern names and values,
|
|
// as defined by JSON_NAMEVALUEINTERN[true] global variable
|
|
TDocVariantModel = (
|
|
mVoid,
|
|
mDefault,
|
|
mFast,
|
|
mFastFloat,
|
|
mFastStrict,
|
|
mFastExtended,
|
|
mFastExtendedIntern,
|
|
mNameValue,
|
|
mNameValueExtended,
|
|
mNameValueIntern,
|
|
mNameValueInternExtended);
|
|
|
|
var
|
|
/// some convenient TDocVariant options, e.g. as JSON_[fDefault]
|
|
JSON_: array[TDocVariantModel] of TDocVariantOptions = (
|
|
// mVoid
|
|
[],
|
|
// mDefault
|
|
[dvoReturnNullForUnknownProperty],
|
|
// mFast
|
|
[dvoReturnNullForUnknownProperty,
|
|
dvoValueCopiedByReference],
|
|
// mFastFloat
|
|
[dvoReturnNullForUnknownProperty,
|
|
dvoValueCopiedByReference,
|
|
dvoAllowDoubleValue],
|
|
// mFastStrict
|
|
[dvoReturnNullForUnknownProperty,
|
|
dvoValueCopiedByReference,
|
|
dvoJsonParseDoNotTryCustomVariants],
|
|
// mFastExtended
|
|
[dvoReturnNullForUnknownProperty,
|
|
dvoValueCopiedByReference,
|
|
dvoSerializeAsExtendedJson],
|
|
// mFastExtendedIntern
|
|
[dvoReturnNullForUnknownProperty,
|
|
dvoValueCopiedByReference,
|
|
dvoSerializeAsExtendedJson,
|
|
dvoJsonParseDoNotTryCustomVariants,
|
|
dvoInternNames,
|
|
dvoInternValues],
|
|
// mNameValue
|
|
[dvoReturnNullForUnknownProperty,
|
|
dvoValueCopiedByReference,
|
|
dvoNameCaseSensitive],
|
|
// mNameValueExtended
|
|
[dvoReturnNullForUnknownProperty,
|
|
dvoValueCopiedByReference,
|
|
dvoNameCaseSensitive,
|
|
dvoSerializeAsExtendedJson],
|
|
// mNameValueIntern
|
|
[dvoReturnNullForUnknownProperty,
|
|
dvoValueCopiedByReference,
|
|
dvoNameCaseSensitive,
|
|
dvoInternNames,
|
|
dvoInternValues],
|
|
// mNameValueInternExtended
|
|
[dvoReturnNullForUnknownProperty,
|
|
dvoValueCopiedByReference,
|
|
dvoNameCaseSensitive,
|
|
dvoInternNames,
|
|
dvoInternValues,
|
|
dvoSerializeAsExtendedJson]
|
|
);
|
|
|
|
const
|
|
/// same as JSON_[mFast], but can not be used as PDocVariantOptions
|
|
// - handle only currency for floating point values: use JSON_FAST_FLOAT
|
|
// if you want to support double values, with potential precision loss
|
|
JSON_FAST =
|
|
[dvoReturnNullForUnknownProperty,
|
|
dvoValueCopiedByReference];
|
|
|
|
/// same as JSON_FAST, but including dvoAllowDoubleValue for floating
|
|
// point values parsing into double, with potential precision loss
|
|
JSON_FAST_FLOAT =
|
|
[dvoReturnNullForUnknownProperty,
|
|
dvoValueCopiedByReference,
|
|
dvoAllowDoubleValue];
|
|
|
|
var
|
|
/// TDocVariant options which may be used for plain JSON parsing
|
|
// - this won't recognize any extended syntax
|
|
JSON_FAST_STRICT: TDocVariantOptions;
|
|
|
|
/// TDocVariant options to be used so that JSON serialization would
|
|
// use the unquoted JSON syntax for field names
|
|
// - you could use it e.g. on a TOrm variant published field to
|
|
// reduce the JSON escape process during storage in the database, by
|
|
// customizing your TOrmModel instance:
|
|
// ! (aModel.Props[TOrmMyRecord]['VariantProp'] as TOrmPropInfoRttiVariant).
|
|
// ! DocVariantOptions := JSON_FAST_EXTENDED;
|
|
// or - in a cleaner way - by overriding TOrm.InternalDefineModel():
|
|
// ! class procedure TOrmMyRecord.InternalDefineModel(Props: TOrmProperties);
|
|
// ! begin
|
|
// ! (Props.Fields.ByName('VariantProp') as TOrmPropInfoRttiVariant).
|
|
// ! DocVariantOptions := JSON_FAST_EXTENDED;
|
|
// ! end;
|
|
// or to set all variant fields at once:
|
|
// ! class procedure TOrmMyRecord.InternalDefineModel(Props: TOrmProperties);
|
|
// ! begin
|
|
// ! Props.SetVariantFieldsDocVariantOptions(JSON_FAST_EXTENDED);
|
|
// ! end;
|
|
// - consider using JSON_NAMEVALUE[true] for case-sensitive
|
|
// TSynNameValue-like storage, or JSON_FAST_EXTENDEDINTERN if you
|
|
// expect RawUtf8 names and values interning
|
|
JSON_FAST_EXTENDED: TDocVariantOptions;
|
|
|
|
/// TDocVariant options for JSON serialization with efficient storage
|
|
// - i.e. unquoted JSON syntax for field names and RawUtf8 interning
|
|
// - may be used e.g. for efficient persistence of similar data
|
|
// - consider using JSON_FAST_EXTENDED if you don't expect
|
|
// RawUtf8 names and values interning, or need BSON variants parsing
|
|
JSON_FAST_EXTENDEDINTERN: TDocVariantOptions;
|
|
|
|
/// TDocVariant options to be used for case-sensitive TSynNameValue-like
|
|
// storage, with optional extended JSON syntax serialization
|
|
// - consider using JSON_FAST_EXTENDED for case-insensitive objects
|
|
JSON_NAMEVALUE: TDocVariantOptionsBool;
|
|
|
|
/// TDocVariant options to be used for case-sensitive TSynNameValue-like
|
|
// storage, RawUtf8 interning and optional extended JSON syntax serialization
|
|
// - consider using JSON_FAST_EXTENDED for case-insensitive objects,
|
|
// or JSON_NAMEVALUE[] if you don't expect names and values interning
|
|
JSON_NAMEVALUEINTERN: TDocVariantOptionsBool;
|
|
|
|
// - JSON_OPTIONS[false] is e.g. _Json() and _JsonFmt() functions default
|
|
// - JSON_OPTIONS[true] are used e.g. by _JsonFast() and _JsonFastFmt() functions
|
|
// - handle only currency for floating point values: use JSON_FAST_FLOAT/JSON_[mFastFloat]
|
|
// if you want to support double values, with potential precision loss
|
|
JSON_OPTIONS: TDocVariantOptionsBool;
|
|
|
|
// some slightly more verbose backward compatible options
|
|
{$ifndef PUREMORMOT2}
|
|
JSON_OPTIONS_FAST_STRICT: TDocVariantOptions
|
|
absolute JSON_FAST_STRICT;
|
|
JSON_OPTIONS_NAMEVALUE: TDocVariantOptionsBool
|
|
absolute JSON_NAMEVALUE;
|
|
JSON_OPTIONS_NAMEVALUEINTERN: TDocVariantOptionsBool
|
|
absolute JSON_NAMEVALUEINTERN;
|
|
JSON_OPTIONS_FAST_EXTENDED: TDocVariantOptions
|
|
absolute JSON_FAST_EXTENDED;
|
|
JSON_OPTIONS_FAST_EXTENDEDINTERN: TDocVariantOptions
|
|
absolute JSON_FAST_EXTENDEDINTERN;
|
|
|
|
const
|
|
JSON_OPTIONS_FAST = JSON_FAST;
|
|
JSON_OPTIONS_FAST_FLOAT = JSON_FAST_FLOAT;
|
|
{$endif PUREMORMOT2}
|
|
|
|
|
|
type
|
|
/// pointer to a TDocVariant storage
|
|
// - since variants may be stored by reference (i.e. as varByRef), it may
|
|
// be a good idea to use such a pointer via DocVariantData(aVariant)^ or
|
|
// _Safe(aVariant)^ instead of TDocVariantData(aVariant),
|
|
// if you are not sure how aVariant was allocated (may be not _Obj/_Json)
|
|
// - note: due to a local variable lifetime change in Delphi 11, don't use
|
|
// this function with a temporary variant (e.g. from TList<variant>.GetItem) -
|
|
// call _DV() and a local TDocVariantData instead of a PDocVariantData
|
|
PDocVariantData = ^TDocVariantData;
|
|
|
|
/// pointer to a dynamic array of TDocVariant storage
|
|
PDocVariantDataDynArray = array of PDocVariantData;
|
|
|
|
/// define the TDocVariant storage layout
|
|
// - if it has no name property, it is a dvArray
|
|
// - if it has one or more named properties, it is a dvObject
|
|
TDocVariantKind = (
|
|
dvUndefined,
|
|
dvArray,
|
|
dvObject);
|
|
|
|
/// exception class associated to TDocVariant JSON/BSON document
|
|
EDocVariant = class(ESynException)
|
|
protected
|
|
class procedure RaiseSafe(Kind: TDocVariantKind);
|
|
end;
|
|
|
|
/// a custom variant type used to store any JSON/BSON document-based content
|
|
// - i.e. name/value pairs for objects, or an array of values (including
|
|
// nested documents), stored in a TDocVariantData memory structure
|
|
// - you can use _Obj()/_ObjFast() _Arr()/_ArrFast() _Json()/_JsonFast() or
|
|
// _JsonFmt()/_JsonFastFmt() functions to create instances of such variants
|
|
// - property access may be done via late-binding - with some restrictions
|
|
// for older versions of FPC, e.g. allowing to write:
|
|
// ! TDocVariant.NewFast(aVariant);
|
|
// ! aVariant.Name := 'John';
|
|
// ! aVariant.Age := 35;
|
|
// ! writeln(aVariant.Name,' is ',aVariant.Age,' years old');
|
|
// - it also supports a small set of pseudo-properties or pseudo-methods:
|
|
// ! aVariant._Count = DocVariantData(aVariant).Count
|
|
// ! aVariant._Kind = ord(DocVariantData(aVariant).Kind)
|
|
// ! aVariant._JSON = DocVariantData(aVariant).JSON
|
|
// ! aVariant._(i) = DocVariantData(aVariant).Value[i]
|
|
// ! aVariant.Value(i) = DocVariantData(aVariant).Value[i]
|
|
// ! aVariant.Value(aName) = DocVariantData(aVariant).Value[aName]
|
|
// ! aVariant.Name(i) = DocVariantData(aVariant).Name[i]
|
|
// ! aVariant.Add(aItem) = DocVariantData(aVariant).AddItem(aItem)
|
|
// ! aVariant._ := aItem = DocVariantData(aVariant).AddItem(aItem)
|
|
// ! aVariant.Add(aName,aValue) = DocVariantData(aVariant).AddValue(aName,aValue)
|
|
// ! aVariant.Exists(aName) = DocVariantData(aVariant).GetValueIndex(aName)>=0
|
|
// ! aVariant.Delete(i) = DocVariantData(aVariant).Delete(i)
|
|
// ! aVariant.Delete(aName) = DocVariantData(aVariant).Delete(aName)
|
|
// ! aVariant.NameIndex(aName) = DocVariantData(aVariant).GetValueIndex(aName)
|
|
// - it features direct JSON serialization/unserialization, e.g.:
|
|
// ! assert(_Json('["one",2,3]')._JSON='["one",2,3]');
|
|
// - it features direct trans-typing into a string encoded as JSON, e.g.:
|
|
// ! assert(_Json('["one",2,3]')='["one",2,3]');
|
|
TDocVariant = class(TSynInvokeableVariantType)
|
|
protected
|
|
/// name and values interning are shared among all TDocVariantData instances
|
|
fInternNames, fInternValues: TRawUtf8Interning;
|
|
fInternSafe: TLightLock; // just protect TRawUtf8Interning initialization
|
|
function CreateInternNames: TRawUtf8Interning;
|
|
function CreateInternValues: TRawUtf8Interning;
|
|
public
|
|
/// initialize a variant instance to store some document-based content
|
|
// - by default, every internal value will be copied, so access of nested
|
|
// properties can be slow - if you expect the data to be read-only or not
|
|
// propagated into another place, set aOptions=[dvoValueCopiedByReference]
|
|
// will increase the process speed a lot
|
|
class procedure New(out aValue: variant;
|
|
aOptions: TDocVariantOptions = []); overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// initialize a variant instance to store per-reference document-based content
|
|
// - same as New(aValue, JSON_FAST);
|
|
// - to be used e.g. as
|
|
// !var
|
|
// ! v: variant;
|
|
// !begin
|
|
// ! TDocVariant.NewFast(v);
|
|
// ! ...
|
|
class procedure NewFast(out aValue: variant;
|
|
aKind: TDocVariantKind = dvUndefined); overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// ensure a variant is a TDocVariant instance
|
|
// - if aValue is not a TDocVariant, will create a new JSON_FAST
|
|
class procedure IsOfTypeOrNewFast(var aValue: variant);
|
|
/// initialize several variant instances to store document-based content
|
|
// - replace several calls to TDocVariantData.InitFast
|
|
// - to be used e.g. as
|
|
// !var
|
|
// ! v1, v2, v3: TDocVariantData;
|
|
// !begin
|
|
// ! TDocVariant.NewFast([@v1,@v2,@v3]);
|
|
// ! ...
|
|
class procedure NewFast(const aValues: array of PDocVariantData;
|
|
aKind: TDocVariantKind = dvUndefined); overload;
|
|
/// initialize a variant instance to store some document-based content
|
|
// - you can use this function to create a variant, which can be nested into
|
|
// another document, e.g.:
|
|
// ! aVariant := TDocVariant.New;
|
|
// ! aVariant.id := 10;
|
|
// - by default, every internal value will be copied, so access of nested
|
|
// properties can be slow - if you expect the data to be read-only or not
|
|
// propagated into another place, set Options=[dvoValueCopiedByReference]
|
|
// will increase the process speed a lot
|
|
// - in practice, you should better use _Obj()/_ObjFast() _Arr()/_ArrFast()
|
|
// functions or TDocVariant.NewFast()
|
|
class function New(Options: TDocVariantOptions = []): variant; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// initialize a variant instance to store some document-based object content
|
|
// - object will be initialized with data supplied two by two, as Name,Value
|
|
// pairs, e.g.
|
|
// ! aVariant := TDocVariant.NewObject(['name','John','year',1972]);
|
|
// which is the same as:
|
|
// ! TDocVariant.New(aVariant);
|
|
// ! TDocVariantData(aVariant).AddValue('name','John');
|
|
// ! TDocVariantData(aVariant).AddValue('year',1972);
|
|
// - by default, every internal value will be copied, so access of nested
|
|
// properties can be slow - if you expect the data to be read-only or not
|
|
// propagated into another place, set Options=[dvoValueCopiedByReference]
|
|
// will increase the process speed a lot
|
|
// - in practice, you should better use the function _Obj() which is a
|
|
// wrapper around this class method
|
|
class function NewObject(const NameValuePairs: array of const;
|
|
Options: TDocVariantOptions = []): variant;
|
|
/// initialize a variant instance to store some document-based array content
|
|
// - array will be initialized with data supplied as parameters, e.g.
|
|
// ! aVariant := TDocVariant.NewArray(['one',2,3.0]);
|
|
// which is the same as:
|
|
// ! TDocVariant.New(aVariant);
|
|
// ! TDocVariantData(aVariant).AddItem('one');
|
|
// ! TDocVariantData(aVariant).AddItem(2);
|
|
// ! TDocVariantData(aVariant).AddItem(3.0);
|
|
// - by default, every internal value will be copied, so access of nested
|
|
// properties can be slow - if you expect the data to be read-only or not
|
|
// propagated into another place, set aOptions=[dvoValueCopiedByReference]
|
|
// will increase the process speed a lot
|
|
// - in practice, you should better use the function _Arr() which is a
|
|
// wrapper around this class method
|
|
class function NewArray(const Items: array of const;
|
|
Options: TDocVariantOptions = []): variant; overload;
|
|
/// initialize a variant instance to store some document-based array content
|
|
// - array will be initialized with data supplied dynamic array of variants
|
|
class function NewArray(const Items: TVariantDynArray;
|
|
Options: TDocVariantOptions = []): variant; overload;
|
|
/// initialize a variant instance to store some document-based object content
|
|
// from a supplied (extended) JSON content
|
|
// - in addition to the JSON RFC specification strict mode, this method will
|
|
// handle some BSON-like extensions, e.g. unquoted field names
|
|
// - a private copy of the incoming JSON buffer will be used, then
|
|
// it will call the TDocVariantData.InitJsonInPlace() method
|
|
// - to be used e.g. as:
|
|
// ! var V: variant;
|
|
// ! begin
|
|
// ! V := TDocVariant.NewJson('{"id":10,"doc":{"name":"John","birthyear":1972}}');
|
|
// ! assert(V.id=10);
|
|
// ! assert(V.doc.name='John');
|
|
// ! assert(V.doc.birthYear=1972);
|
|
// ! // and also some pseudo-properties:
|
|
// ! assert(V._count=2);
|
|
// ! assert(V.doc._kind=ord(dvObject));
|
|
// - or with a JSON array:
|
|
// ! V := TDocVariant.NewJson('["one",2,3]');
|
|
// ! assert(V._kind=ord(dvArray));
|
|
// ! for i := 0 to V._count-1 do
|
|
// ! writeln(V._(i));
|
|
// - by default, every internal value will be copied, so access of nested
|
|
// properties can be slow - if you expect the data to be read-only or not
|
|
// propagated into another place, add dvoValueCopiedByReference in Options
|
|
// will increase the process speed a lot
|
|
// - in practice, you should better use the function _Json()/_JsonFast()
|
|
// which are handy wrappers around this class method
|
|
class function NewJson(const Json: RawUtf8;
|
|
Options: TDocVariantOptions = [dvoReturnNullForUnknownProperty]): variant;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// initialize a variant instance to store some document-based object content
|
|
// from a supplied existing TDocVariant instance
|
|
// - use it on a value returned as varByRef (e.g. by _() pseudo-method),
|
|
// to ensure the returned variant will behave as a stand-alone value
|
|
// - for instance, the following:
|
|
// ! oSeasons := TDocVariant.NewUnique(o.Seasons);
|
|
// is the same as:
|
|
// ! oSeasons := o.Seasons;
|
|
// ! _Unique(oSeasons);
|
|
// or even:
|
|
// ! oSeasons := _Copy(o.Seasons);
|
|
class function NewUnique(const SourceDocVariant: variant;
|
|
Options: TDocVariantOptions = [dvoReturnNullForUnknownProperty]): variant;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// will return the unique element of a TDocVariant array or a default
|
|
// - if the value is a dvArray with one single item, returns this value
|
|
// - if the value is not a TDocVariant nor a dvArray with one single item,
|
|
// it wil return the default value
|
|
class procedure GetSingleOrDefault(const docVariantArray, default: variant;
|
|
var result: variant);
|
|
|
|
/// finalize the stored information
|
|
destructor Destroy; override;
|
|
/// used by dvoInternNames for string interning of all Names[] values
|
|
function InternNames: TRawUtf8Interning;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// used by dvoInternValues for string interning of all RawUtf8 Values[]
|
|
function InternValues: TRawUtf8Interning;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
// this implementation will write the content as JSON object or array
|
|
procedure ToJson(W: TJsonWriter; Value: PVarData); override;
|
|
/// will check if the value is an array, and return the number of items
|
|
// - if the document is an array, will return the items count (0 meaning
|
|
// void array) - used e.g. by TSynMustacheContextVariant
|
|
// - this overridden method will implement it for dvArray instance kind
|
|
function IterateCount(const V: TVarData;
|
|
GetObjectAsValues: boolean): integer; override;
|
|
/// allow to loop over an array document
|
|
// - Index should be in 0..IterateCount-1 range
|
|
// - this default implementation will do handle dvArray instance kind
|
|
procedure Iterate(var Dest: TVarData; const V: TVarData;
|
|
Index: integer); override;
|
|
/// returns true if this document has Count = 0
|
|
function IsVoid(const V: TVarData): boolean; override;
|
|
/// low-level callback to access internal pseudo-methods
|
|
// - mainly the _(Index: integer): variant method to retrieve an item
|
|
// if the document is an array
|
|
function DoFunction(var Dest: TVarData; const V: TVarData;
|
|
const Name: string; const Arguments: TVarDataArray): boolean; override;
|
|
/// low-level callback to access internal pseudo-methods
|
|
function DoProcedure(const V: TVarData; const Name: string;
|
|
const Arguments: TVarDataArray): boolean; override;
|
|
/// low-level callback to clear the content
|
|
procedure Clear(var V: TVarData); override;
|
|
/// low-level callback to copy two variant content
|
|
// - such copy will by default be done by-value, for safety
|
|
// - if you are sure you will use the variants as read-only, you can set
|
|
// the dvoValueCopiedByReference Option to use faster by-reference copy
|
|
procedure Copy(var Dest: TVarData; const Source: TVarData;
|
|
const Indirect: boolean); override;
|
|
/// copy two variant content by value
|
|
// - overridden method since instance may use a by-reference copy pattern
|
|
procedure CopyByValue(var Dest: TVarData; const Source: TVarData); override;
|
|
/// handle type conversion
|
|
// - only types processed by now are string/OleStr/UnicodeString/date
|
|
procedure Cast(var Dest: TVarData; const Source: TVarData); override;
|
|
/// handle type conversion
|
|
// - only types processed by now are string/OleStr/UnicodeString/date
|
|
procedure CastTo(var Dest: TVarData; const Source: TVarData;
|
|
const AVarType: TVarType); override;
|
|
/// overriden method for actual getter by name implementation
|
|
function IntGet(var Dest: TVarData; const Instance: TVarData;
|
|
Name: PAnsiChar; NameLen: PtrInt; NoException: boolean): boolean; override;
|
|
/// overriden method for actual setter by name implementation
|
|
function IntSet(const Instance, Value: TVarData;
|
|
Name: PAnsiChar; NameLen: PtrInt): boolean; override;
|
|
/// overriden method redirecting to TDocVariantData.Compare()
|
|
function IntCompare(const Instance, Another: TVarData;
|
|
CaseInsensitive: boolean): integer; override;
|
|
end;
|
|
|
|
/// method used by TDocVariantData.ReduceAsArray to filter each object
|
|
// - should return TRUE if the item match the expectations
|
|
TOnReducePerItem = function(Item: PDocVariantData): boolean of object;
|
|
|
|
/// method used by TDocVariantData.ReduceAsArray to filter each object
|
|
// - should return TRUE if the item match the expectations
|
|
TOnReducePerValue = function(const Value: variant): boolean of object;
|
|
|
|
{$ifdef HASITERATORS}
|
|
/// internal state engine used by TDocVariant enumerators records
|
|
TDocVariantEnumeratorState = record
|
|
private
|
|
Curr, After: PVariant;
|
|
public
|
|
procedure Init(Values: PVariantArray; Count: PtrUInt); inline;
|
|
procedure Void; inline;
|
|
function MoveNext: boolean; inline;
|
|
end;
|
|
|
|
/// local iterated name/value pair as returned by TDocVariantData.GetEnumerator
|
|
// and TDocVariantData.Fields
|
|
// - we use pointers for best performance - but warning: Name may be nil for
|
|
// TDocVariantData.GetEnumerator over an array
|
|
TDocVariantFields = record
|
|
/// points to current Name[] - nil if the TDocVariantData is an array
|
|
Name: PRawUtf8;
|
|
/// points to the current Value[] - never nil
|
|
Value: PVariant;
|
|
end;
|
|
|
|
/// low-level Enumerator as returned by TDocVariantData.GetEnumerator
|
|
// (default "for .. in dv do") and TDocVariantData.Fields
|
|
TDocVariantFieldsEnumerator = record
|
|
private
|
|
State: TDocVariantEnumeratorState;
|
|
Name: PRawUtf8;
|
|
function GetCurrent: TDocVariantFields; inline;
|
|
public
|
|
function MoveNext: boolean; inline;
|
|
function GetEnumerator: TDocVariantFieldsEnumerator; inline;
|
|
/// returns the current Name/Value or Value as pointers in TDocVariantFields
|
|
property Current: TDocVariantFields
|
|
read GetCurrent;
|
|
end;
|
|
|
|
/// low-level Enumerator as returned by TDocVariantData.FieldNames
|
|
TDocVariantFieldNamesEnumerator = record
|
|
private
|
|
Curr, After: PRawUtf8;
|
|
public
|
|
function MoveNext: boolean; inline;
|
|
function GetEnumerator: TDocVariantFieldNamesEnumerator; inline;
|
|
/// returns the current Name/Value or Value as pointers in TDocVariantFields
|
|
property Current: PRawUtf8
|
|
read Curr;
|
|
end;
|
|
|
|
/// low-level Enumerator as returned by TDocVariantData.Items and FieldValues
|
|
TDocVariantItemsEnumerator = record
|
|
private
|
|
State: TDocVariantEnumeratorState;
|
|
public
|
|
function MoveNext: boolean; inline;
|
|
function GetEnumerator: TDocVariantItemsEnumerator; inline;
|
|
/// returns the current Value as pointer
|
|
property Current: PVariant
|
|
read State.Curr;
|
|
end;
|
|
|
|
/// low-level Enumerator as returned by TDocVariantData.Objects
|
|
TDocVariantObjectsEnumerator = record
|
|
private
|
|
State: TDocVariantEnumeratorState;
|
|
Value: PDocVariantData;
|
|
public
|
|
function MoveNext: boolean; {$ifdef HASSAFEINLINE} inline; {$endif}
|
|
function GetEnumerator: TDocVariantObjectsEnumerator; inline;
|
|
/// returns the current Value as pointer to each TDocVariantData object
|
|
property Current: PDocVariantData
|
|
read Value;
|
|
end;
|
|
{$endif HASITERATORS}
|
|
|
|
/// how duplicated values could be searched
|
|
TSearchDuplicate = (
|
|
sdNone,
|
|
sdCaseSensitive,
|
|
sdCaseInsensitive);
|
|
|
|
{$A-} { packet object not allowed since Delphi 2009 :( }
|
|
/// memory structure used for TDocVariant storage of any JSON/BSON
|
|
// document-based content as variant
|
|
// - i.e. name/value pairs for objects, or an array of values (including
|
|
// nested documents)
|
|
// - you can use _Obj()/_ObjFast() _Arr()/_ArrFast() _Json()/_JsonFast() or
|
|
// _JsonFmt()/_JsonFastFmt() functions to create instances of such variants
|
|
// - you can transtype such an allocated variant into TDocVariantData
|
|
// to access directly its internals (like Count or Values[]/Names[]):
|
|
// ! aVariantObject := TDocVariant.NewObject(['name','John','year',1972]);
|
|
// ! aVariantObject := _ObjFast(['name','John','year',1972]);
|
|
// ! with _Safe(aVariantObject)^ do
|
|
// ! for i := 0 to Count-1 do
|
|
// ! writeln(Names[i],'=',Values[i]); // for an object
|
|
// ! aVariantArray := TDocVariant.NewArray(['one',2,3.0]);
|
|
// ! aVariantArray := _JsonFast('["one",2,3.0]');
|
|
// ! with _Safe(aVariantArray)^ do
|
|
// ! for i := 0 to Count-1 do
|
|
// ! writeln(Values[i]); // for an array
|
|
// - use "with _Safe(...)^ do" and not "with TDocVariantData(...) do" as the
|
|
// former will handle internal variant redirection (varByRef), e.g. from late
|
|
// binding or assigned another TDocVariant
|
|
// - Delphi "object" is buggy on stack -> also defined as record with methods
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TDocVariantData = record
|
|
{$else}
|
|
TDocVariantData = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
private
|
|
// note: this structure uses all TVarData available space: no filler needed!
|
|
VType: TVarType; // 16-bit
|
|
VOptions: TDocVariantOptions; // 16-bit
|
|
VCount: integer; // 32-bit
|
|
VName: TRawUtf8DynArray; // pointer
|
|
VValue: TVariantDynArray; // pointer
|
|
// retrieve the value as varByRef
|
|
function GetValueOrItem(const aNameOrIndex: variant): variant;
|
|
procedure SetValueOrItem(const aNameOrIndex, aValue: variant);
|
|
// kind is stored as dvoIsArray/dvoIsObject within VOptions
|
|
function GetKind: TDocVariantKind;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
function Has(dvo: TDocVariantOption): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
procedure Include(dvo: TDocVariantOption);
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
procedure SetOptions(const opt: TDocVariantOptions); // keep dvoIsObject/Array
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
// capacity is Length(VValue) and Length(VName)
|
|
procedure SetCapacity(aValue: integer);
|
|
function GetCapacity: integer;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
// implement U[] I[] B[] D[] O[] O_[] A[] A_[] _[] properties
|
|
function GetOrAddIndexByName(const aName: RawUtf8): integer;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
function GetOrAddPVariantByName(const aName: RawUtf8): PVariant;
|
|
function GetPVariantByName(const aName: RawUtf8): PVariant;
|
|
function GetRawUtf8ByName(const aName: RawUtf8): RawUtf8;
|
|
procedure SetRawUtf8ByName(const aName, aValue: RawUtf8);
|
|
function GetStringByName(const aName: RawUtf8): string;
|
|
procedure SetStringByName(const aName: RawUtf8; const aValue: string);
|
|
function GetInt64ByName(const aName: RawUtf8): Int64;
|
|
procedure SetInt64ByName(const aName: RawUtf8; const aValue: Int64);
|
|
function GetBooleanByName(const aName: RawUtf8): boolean;
|
|
procedure SetBooleanByName(const aName: RawUtf8; aValue: boolean);
|
|
function GetDoubleByName(const aName: RawUtf8): Double;
|
|
procedure SetDoubleByName(const aName: RawUtf8; const aValue: Double);
|
|
function GetDocVariantExistingByName(const aName: RawUtf8;
|
|
aNotMatchingKind: TDocVariantKind): PDocVariantData;
|
|
function GetObjectExistingByName(const aName: RawUtf8): PDocVariantData;
|
|
function GetDocVariantOrAddByName(const aName: RawUtf8;
|
|
aKind: TDocVariantKind): PDocVariantData;
|
|
function GetObjectOrAddByName(const aName: RawUtf8): PDocVariantData;
|
|
function GetArrayExistingByName(const aName: RawUtf8): PDocVariantData;
|
|
function GetArrayOrAddByName(const aName: RawUtf8): PDocVariantData;
|
|
function GetAsDocVariantByIndex(aIndex: integer): PDocVariantData;
|
|
function GetVariantByPath(const aNameOrPath: RawUtf8): Variant;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
function GetObjectProp(const aName: RawUtf8; out aFound: PVariant;
|
|
aPreviousIndex: PInteger): boolean;
|
|
function InternalAddBuf(aName: PUtf8Char; aNameLen: integer): integer;
|
|
procedure InternalSetValue(aIndex: PtrInt; const aValue: variant);
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
procedure InternalSetVarRec(aIndex: PtrInt; const aValue: TVarRec);
|
|
{$ifdef HASSAFEINLINE}inline;{$endif}
|
|
procedure InternalUniqueValueAt(aIndex: PtrInt);
|
|
function InternalNextPath(var aCsv: PUtf8Char; aName: PShortString;
|
|
aPathDelim: AnsiChar): PtrInt;
|
|
{$ifdef FPC}inline;{$endif}
|
|
procedure InternalNotFound(var Dest: variant; aName: PUtf8Char); overload;
|
|
procedure InternalNotFound(var Dest: variant; aIndex: integer); overload;
|
|
function InternalNotFound(aName: PUtf8Char): PVariant; overload;
|
|
function InternalNotFound(aIndex: integer): PDocVariantData; overload;
|
|
function RangeVoid(var Offset, Limit: integer): boolean;
|
|
procedure ClearFast;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
public
|
|
/// initialize a TDocVariantData to store some document-based content
|
|
// - can be used with a stack-allocated TDocVariantData variable:
|
|
// !var
|
|
// ! Doc: TDocVariantData; // stack-allocated variable
|
|
// !begin
|
|
// ! Doc.Init;
|
|
// ! Doc.AddValue('name','John');
|
|
// ! assert(Doc.Value['name']='John');
|
|
// ! assert(variant(Doc).name='John');
|
|
// !end;
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
procedure Init(const aOptions: TDocVariantOptions = []); overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// initialize a TDocVariantData to store a content of some known type
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
procedure Init(const aOptions: TDocVariantOptions;
|
|
aKind: TDocVariantKind); overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// initialize a TDocVariantData to store some document-based content
|
|
// - use the options corresponding to the supplied TDocVariantModel
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
procedure Init(aModel: TDocVariantModel;
|
|
aKind: TDocVariantKind = dvUndefined); overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// initialize a TDocVariantData to store per-reference document-based content
|
|
// - same as Doc.Init(JSON_FAST);
|
|
// - can be used with a stack-allocated TDocVariantData variable:
|
|
// !var
|
|
// ! Doc: TDocVariantData; // stack-allocated variable
|
|
// !begin
|
|
// ! Doc.InitFast;
|
|
// ! Doc.AddValue('name','John');
|
|
// ! assert(Doc.Value['name']='John');
|
|
// ! assert(variant(Doc).name='John');
|
|
// !end;
|
|
// - see also TDocVariant.NewFast() if you want to initialize several
|
|
// TDocVariantData variable instances at once
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
procedure InitFast(aKind: TDocVariantKind = dvUndefined); overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// initialize a TDocVariantData to store per-reference document-based content
|
|
// - this overloaded method allows to specify an estimation of how many
|
|
// properties or items this aKind document would contain
|
|
procedure InitFast(InitialCapacity: integer; aKind: TDocVariantKind); overload;
|
|
/// initialize a TDocVariantData to store document-based object content
|
|
// - object will be initialized with data supplied two by two, as Name,Value
|
|
// pairs, e.g.
|
|
// !var
|
|
// ! Doc: TDocVariantData; // stack-allocated variable
|
|
// !begin
|
|
// ! Doc.InitObject(['name','John','year',1972]);
|
|
// which is the same as:
|
|
// ! var Doc: TDocVariantData;
|
|
// !begin
|
|
// ! Doc.Init;
|
|
// ! Doc.AddValue('name','John');
|
|
// ! Doc.AddValue('year',1972);
|
|
// - this method is called e.g. by _Obj() and _ObjFast() global functions
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
procedure InitObject(const NameValuePairs: array of const;
|
|
aOptions: TDocVariantOptions = []); overload;
|
|
/// initialize a TDocVariantData to store document-based object content
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
procedure InitObject(const NameValuePairs: array of const;
|
|
Model: TDocVariantModel); overload;
|
|
/// initialize a variant instance to store some document-based array content
|
|
// - array will be initialized with data supplied as parameters, e.g.
|
|
// !var
|
|
// ! Doc: TDocVariantData; // stack-allocated variable
|
|
// !begin
|
|
// ! Doc.InitArray(['one',2,3.0]);
|
|
// ! assert(Doc.Count=3);
|
|
// !end;
|
|
// which is the same as:
|
|
// ! var Doc: TDocVariantData;
|
|
// ! i: integer;
|
|
// !begin
|
|
// ! Doc.Init;
|
|
// ! Doc.AddItem('one');
|
|
// ! Doc.AddItem(2);
|
|
// ! Doc.AddItem(3.0);
|
|
// ! assert(Doc.Count=3);
|
|
// ! for i := 0 to Doc.Count-1 do
|
|
// ! writeln(Doc.Value[i]);
|
|
// !end;
|
|
// - this method is called e.g. by _Arr() and _ArrFast() global functions
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
procedure InitArray(const aItems: array of const;
|
|
aOptions: TDocVariantOptions = []); overload;
|
|
/// initialize a variant instance to store some document-based array content
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
procedure InitArray(const aItems: array of const;
|
|
aModel: TDocVariantModel); overload;
|
|
/// initialize a variant instance to store some document-based array content
|
|
// - array will be initialized with data supplied as variant dynamic array
|
|
// - if Items is [], the variant will be set as null
|
|
// - will be almost immediate, since TVariantDynArray is reference-counted,
|
|
// unless ItemsCopiedByReference is set to FALSE
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
procedure InitArrayFromVariants(const aItems: TVariantDynArray;
|
|
aOptions: TDocVariantOptions = [];
|
|
aItemsCopiedByReference: boolean = true; aCount: integer = -1);
|
|
/// initialize a variant array instance from an object Values[]
|
|
procedure InitArrayFromObjectValues(const aObject: variant;
|
|
aOptions: TDocVariantOptions = []; aItemsCopiedByReference: boolean = true);
|
|
/// initialize a variant array instance from an object Names[]
|
|
procedure InitArrayFromObjectNames(const aObject: variant;
|
|
aOptions: TDocVariantOptions = []; aItemsCopiedByReference: boolean = true);
|
|
/// initialize a variant instance from some 'a,b,c' CSV one-line content
|
|
// - is by default separator tolerant, i.e. will detect ',' ';' or #9 in text
|
|
procedure InitArrayFromCsv(const aCsv: RawUtf8;
|
|
aOptions: TDocVariantOptions; aSeparator: AnsiChar = #0;
|
|
aTrimItems: boolean = false; aAddVoidItems: boolean = false;
|
|
aQuote: AnsiChar = #0);
|
|
/// initialize a variant instance from a CSV file content with header
|
|
// - stored objects names will be retrieved from the first CSV line
|
|
// - is by default separator tolerant, i.e. will detect ',' ';' or #9 in text
|
|
procedure InitArrayFromCsvFile(const aCsv: RawUtf8;
|
|
aOptions: TDocVariantOptions; aSeparator: AnsiChar = #0;
|
|
aQuote: AnsiChar = #0);
|
|
/// create a TDocVariant array, from a sub-range of this document array
|
|
// - returned variant instance is a dvArray containing only the specified rows
|
|
// $ new.InitArrayFrom(src) returns a copy of src array
|
|
// $ new.InitArrayFrom(src, 10) returns items 10..Count-1 of src
|
|
// $ new.InitArrayFrom(src, 0, 10) returns first 0..9 items of src
|
|
// $ new.InitArrayFrom(src, 10, 20) returns items 10..29 - truncated if Count<30
|
|
// $ new.InitArrayFrom(src, -10) returns last Count-10..Count-1 items of src
|
|
procedure InitArrayFrom(const aSource: TDocVariantData;
|
|
aOptions: TDocVariantOptions; aOffset: integer = 0; aLimit: integer = 0); overload;
|
|
/// initialize a variant instance to store some RawUtf8 array content
|
|
procedure InitArrayFrom(const aItems: TRawUtf8DynArray;
|
|
aOptions: TDocVariantOptions; aCount: integer = -1); overload;
|
|
/// initialize a variant instance to store some 32-bit integer array content
|
|
procedure InitArrayFrom(const aItems: TIntegerDynArray;
|
|
aOptions: TDocVariantOptions; aCount: integer = -1); overload;
|
|
/// initialize a variant instance to store some 64-bit integer array content
|
|
procedure InitArrayFrom(const aItems: TInt64DynArray;
|
|
aOptions: TDocVariantOptions; aCount: integer = -1); overload;
|
|
/// initialize a variant instance to store some double array content
|
|
procedure InitArrayFrom(const aItems: TDoubleDynArray;
|
|
aOptions: TDocVariantOptions; aCount: integer = -1); overload;
|
|
/// initialize a variant instance to store some dynamic array content
|
|
procedure InitArrayFrom(var aItems; ArrayInfo: PRttiInfo;
|
|
aOptions: TDocVariantOptions; ItemsCount: PInteger = nil); overload;
|
|
/// initialize a variant instance to store some TDynArray content
|
|
procedure InitArrayFrom(const aItems: TDynArray;
|
|
aOptions: TDocVariantOptions = JSON_FAST_FLOAT); overload;
|
|
/// initialize a variant instance to store a T*ObjArray content
|
|
// - will call internally ObjectToVariant() to make the conversion
|
|
procedure InitArrayFromObjArray(const ObjArray; aOptions: TDocVariantOptions;
|
|
aWriterOptions: TTextWriterWriteObjectOptions = [woDontStoreDefault];
|
|
aCount: integer = -1);
|
|
/// fill a TDocVariant array from standard or non-expanded JSON ORM/DB result
|
|
// - accept the ORM/DB results dual formats as recognized by TOrmTableJson,
|
|
// i.e. both [{"f1":"1v1","f2":1v2},{"f2":"2v1","f2":2v2}...] and
|
|
// {"fieldCount":2,"values":["f1","f2","1v1",1v2,"2v1",2v2...],"rowCount":20}
|
|
// - about 2x (expanded) or 3x (non-expanded) faster than Doc.InitJsonInPlace()
|
|
// - will also use less memory, because all object field names will be shared
|
|
// - in expanded mode, the fields order won't be checked, as with TOrmTableJson
|
|
// - warning: the incoming JSON buffer will be modified in-place: so you should
|
|
// make a private copy before running this method, as overloaded procedures do
|
|
// - some numbers on a Core i5-13500, extracted from our regression tests:
|
|
// $ TDocVariant InitJsonInPlace in 72.91ms i.e. 2.1M rows/s, 268.8 MB/s
|
|
// $ TDocVariant InitJsonInPlace no guess in 69.49ms i.e. 2.2M rows/s, 282 MB/s
|
|
// $ TDocVariant InitJsonInPlace dvoIntern in 68.41ms i.e. 2.2M rows/s, 286.5 MB/s
|
|
// $ TDocVariant FromResults exp in 31.69ms i.e. 4.9M rows/s, 618.6 MB/s
|
|
// $ TDocVariant FromResults not exp in 24.48ms i.e. 6.4M rows/s, 352.1 MB/s
|
|
function InitArrayFromResults(Json: PUtf8Char; JsonLen: PtrInt;
|
|
aOptions: TDocVariantOptions = JSON_FAST_FLOAT): boolean; overload;
|
|
/// fill a TDocVariant array from standard or non-expanded JSON ORM/DB result
|
|
// - accept the ORM/DB results dual formats as recognized by TOrmTableJson
|
|
// - about 2x (expanded) or 3x (non-expanded) faster than Doc.InitJson()
|
|
// - will also use less memory, because all object field names will be shared
|
|
// - in expanded mode, the fields order won't be checked, as with TOrmTableJson
|
|
// - a private copy of the incoming JSON buffer will be used before parsing
|
|
function InitArrayFromResults(const Json: RawUtf8;
|
|
aOptions: TDocVariantOptions = JSON_FAST_FLOAT): boolean; overload;
|
|
/// fill a TDocVariant array from standard or non-expanded JSON ORM/DB result
|
|
// - accept the ORM/DB results dual formats as recognized by TOrmTableJson
|
|
// - about 2x (expanded) or 3x (non-expanded) faster than Doc.InitJson()
|
|
// - will also use less memory, because all object field names will be shared
|
|
// - in expanded mode, the fields order won't be checked, as with TOrmTableJson
|
|
// - a private copy of the incoming JSON buffer will be used before parsing
|
|
function InitArrayFromResults(const Json: RawUtf8;
|
|
aModel: TDocVariantModel): boolean; overload;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// initialize a variant instance to store some document-based object content
|
|
// - object will be initialized with names and values supplied as dynamic arrays
|
|
// - if aNames and aValues are [] or do have matching sizes, the variant
|
|
// will be set as null
|
|
// - will be almost immediate, since Names and Values are reference-counted
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
procedure InitObjectFromVariants(const aNames: TRawUtf8DynArray;
|
|
const aValues: TVariantDynArray; aOptions: TDocVariantOptions = []);
|
|
/// initialize a variant instance to store a document-based object with a
|
|
// single property
|
|
// - the supplied path could be 'Main.Second.Third', to create nested
|
|
// objects, e.g. {"Main":{"Second":{"Third":value}}}
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
procedure InitObjectFromPath(const aPath: RawUtf8; const aValue: variant;
|
|
aOptions: TDocVariantOptions = []; aPathDelim: AnsiChar = '.');
|
|
/// initialize a variant instance to store some document-based object content
|
|
// from a supplied JSON array or JSON object content
|
|
// - warning: the incoming JSON buffer will be modified in-place: so you should
|
|
// make a private copy before running this method, as InitJson() does
|
|
// - this method is called e.g. by _JsonFmt() _JsonFastFmt() global functions
|
|
// with a temporary JSON buffer content created from a set of parameters
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
// - consider the faster InitArrayFromResults() from ORM/SQL JSON results
|
|
function InitJsonInPlace(Json: PUtf8Char;
|
|
aOptions: TDocVariantOptions = []; aEndOfObject: PUtf8Char = nil): PUtf8Char;
|
|
/// initialize a variant instance to store some document-based object content
|
|
// from a supplied JSON array or JSON object content
|
|
// - a private copy of the incoming JSON buffer will be used, then
|
|
// it will call the other overloaded InitJsonInPlace() method
|
|
// - this method is called e.g. by _Json() and _JsonFast() global functions
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
// - handle only currency for floating point values: set JSON_FAST_FLOAT
|
|
// or dvoAllowDoubleValue option to support double, with potential precision loss
|
|
// - consider the faster InitArrayFromResults() from ORM/SQL JSON results
|
|
function InitJson(const Json: RawUtf8;
|
|
aOptions: TDocVariantOptions = []): boolean; overload;
|
|
/// initialize a variant instance to store some document-based object content
|
|
// from a supplied JSON array or JSON object content
|
|
// - use the options corresponding to the supplied TDocVariantModel
|
|
// - a private copy of the incoming JSON buffer will be made
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
// - handle only currency for floating point values unless you set mFastFloat
|
|
// - consider the faster InitArrayFromResults() from ORM/SQL JSON results
|
|
function InitJson(const Json: RawUtf8; aModel: TDocVariantModel): boolean; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// initialize a variant instance to store some document-based object content
|
|
// from a file containing some JSON array or JSON object
|
|
// - file may have been serialized using the SaveToJsonFile() method
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
// - handle only currency for floating point values: set JSON_FAST_FLOAT
|
|
// or dvoAllowDoubleValue option to support double, with potential precision loss
|
|
// - will assume text file with no BOM is already UTF-8 encoded
|
|
function InitJsonFromFile(const FileName: TFileName;
|
|
aOptions: TDocVariantOptions = []): boolean;
|
|
/// ensure a document-based variant instance will have one unique options set
|
|
// - this will create a copy of the supplied TDocVariant instance, forcing
|
|
// all nested events to have the same set of Options
|
|
// - you can use this function to ensure that all internal properties of this
|
|
// variant will be copied e.g. per-reference (if you set JSON_[mDefault])
|
|
// or per-value (if you set JSON_[mDefault]) whatever options the nested
|
|
// objects or arrays were created with
|
|
// - will raise an EDocVariant if the supplied variant is not a TDocVariant
|
|
// - you may rather use _Unique() or _UniqueFast() wrappers if you want to
|
|
// ensure that a TDocVariant instance is unique
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
procedure InitCopy(const SourceDocVariant: variant;
|
|
aOptions: TDocVariantOptions);
|
|
/// clone a document-based variant with the very same options but no data
|
|
// - the same options will be used, without the dvArray/dvObject flags
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
procedure InitClone(const CloneFrom: TDocVariantData);
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// low-level copy a document-based variant with the very same options and count
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
// - will copy Count and Names[] by reference, but Values[] only if CloneValues
|
|
// - returns the first item in Values[]
|
|
function InitFrom(const CloneFrom: TDocVariantData; CloneValues: boolean;
|
|
MakeUnique: boolean = false): PVariant;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// initialize a variant instance to store some document-based object content
|
|
// from a supplied name=value list of UTF-8 encoded text (e.g. .ini file)
|
|
// - the supplied content may have been generated by ToTextPairs() method
|
|
// - if ItemSep=#10, then any kind of line feed (CRLF or LF) will be handled
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
procedure InitFromPairs(aPairs: PUtf8Char; aOptions: TDocVariantOptions;
|
|
NameValueSep: AnsiChar = '='; ItemSep: AnsiChar = #10;
|
|
DoTrim: boolean = true); overload;
|
|
/// initialize a variant instance to store some document-based object content
|
|
// from a supplied name=value list of UTF-8 encoded text (e.g. .ini file)
|
|
// - the supplied content may have been generated by ToTextPairs() method
|
|
// - if ItemSep = #10, then any kind of line feed (CRLF or LF) will be handled
|
|
// - if you call Init*() methods in a row, ensure you call Clear in-between
|
|
procedure InitFromPairs(const aPairs: RawUtf8; aOptions: TDocVariantOptions;
|
|
NameValueSep: AnsiChar = '='; ItemSep: AnsiChar = #10;
|
|
DoTrim: boolean = true); overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// to be called before any Init*() method call, when a previous Init*()
|
|
// has already be performed on the same instance, to avoid memory leaks
|
|
// - for instance:
|
|
// !var
|
|
// ! Doc: TDocVariantData; // stack-allocated variable
|
|
// !begin
|
|
// ! Doc.InitArray(['one',2,3.0]); // no need of any Doc.Clear here
|
|
// ! assert(Doc.Count=3);
|
|
// ! Doc.Clear; // to release memory before following InitObject()
|
|
// ! Doc.InitObject(['name','John','year',1972]);
|
|
// !end;
|
|
// - will check the VType, and call ClearFast private method
|
|
procedure Clear;
|
|
/// delete all internal stored values
|
|
// - like Clear + Init() with the same options
|
|
// - will reset Kind to dvUndefined
|
|
procedure Reset;
|
|
/// keep the current Options and Kind, but reset all data and VCount to 0
|
|
procedure Void;
|
|
/// fill all Values[] with #0, then delete all values
|
|
// - could be used to specifically remove sensitive information from memory
|
|
procedure FillZero;
|
|
/// check if the Document is an object - i.e. Kind = dvObject
|
|
function IsObject: boolean;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// check if the Document is an array - i.e. Kind = dvArray
|
|
function IsArray: boolean;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// check if names lookups are case sensitive in this object Document
|
|
function IsCaseSensitive: boolean;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
/// guess the TDocVariantModel corresponding to the current document Options
|
|
// - returns true if model has been found and set
|
|
// - returns false if no JSON_[] matches the current options
|
|
function GetModel(out model: TDocVariantModel): boolean;
|
|
/// low-level method to force a number of items
|
|
// - could be used to fast add items to the internal Values[]/Names[] arrays
|
|
// - just set protected VCount field, do not resize the arrays: caller
|
|
// should ensure that Capacity is big enough and to call Void if aCount=0
|
|
procedure SetCount(aCount: integer);
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// efficient comparison of two TDocVariantData content
|
|
// - will return the same result than JSON comparison, but more efficiently
|
|
function Compare(const Another: TDocVariantData;
|
|
CaseInsensitive: boolean = false): integer; overload;
|
|
/// efficient comparison of two TDocVariantData objects
|
|
// - will always ensure that both this instance and Another are Objects
|
|
// - will compare all supplied Fields values in their specified order
|
|
// - if ObjFields is void, will fallback to regular Compare()
|
|
function CompareObject(const ObjFields: array of RawUtf8;
|
|
const Another: TDocVariantData; CaseInsensitive: boolean = false): integer;
|
|
/// efficient equality comparison of two TDocVariantData content
|
|
// - just a wrapper around Compare(Another)=0
|
|
function Equals(const Another: TDocVariantData;
|
|
CaseInsensitive: boolean = false): boolean; overload;
|
|
{$ifdef HASSAFEINLINE}inline;{$endif}
|
|
/// compare a TTDocVariantData object property with a given value
|
|
// - returns -1 if this instance is not a dvObject or has no aName property
|
|
function Compare(const aName: RawUtf8; const aValue: variant;
|
|
aCaseInsensitive: boolean = false): integer; overload;
|
|
{$ifdef ISDELPHI}{$ifdef HASINLINE}inline;{$endif}{$endif}
|
|
/// efficient equality comparison a TTDocVariantData object property
|
|
function Equals(const aName: RawUtf8; const aValue: variant;
|
|
aCaseInsensitive: boolean = false): boolean; overload;
|
|
{$ifdef ISDELPHI}{$ifdef HASINLINE}inline;{$endif}{$endif}
|
|
/// low-level method called internally to reserve place for new values
|
|
// - returns the index of the newly created item in Values[]/Names[] arrays
|
|
// - you should not have to use it, unless you want to add some items
|
|
// directly within the Values[]/Names[] arrays, using e.g.
|
|
// InitFast(InitialCapacity) to initialize the document
|
|
// - if aName='', append a dvArray item, otherwise append a dvObject field
|
|
// - you can specify an optional aIndex value to Insert instead of Add
|
|
// - warning: FPC optimizer is confused by Values[InternalAdd(name)] so
|
|
// you should call InternalAdd() in an explicit previous step
|
|
function InternalAdd(const aName: RawUtf8; aIndex: integer = -1): integer;
|
|
{$ifdef HASITERATORS}
|
|
/// an enumerator able to compile "for .. in dv do" statements
|
|
// - returns pointers over all Names[] and Values[]
|
|
// - warning: if the document is an array, returned Name is nil:
|
|
// ! var e: TDocVariantFields;
|
|
// ! ...
|
|
// ! dv.InitArray([1, 3, 3, 4]);
|
|
// ! for e in dv do
|
|
// ! // here e.Name = nil
|
|
// ! writeln(e.Value^);
|
|
// ! // output 1 2 3 4
|
|
function GetEnumerator: TDocVariantFieldsEnumerator;
|
|
/// an enumerator able to compile "for .. in dv.Fields do" for objects
|
|
// - returns pointers over all Names[] and Values[]
|
|
// - don't iterate if the document is an array - so Name is never nil:
|
|
// ! var e: TDocVariantFields;
|
|
// ! ...
|
|
// ! dv.InitJson('{a:1,b:2,c:3}');
|
|
// ! for e in dv.Fields do
|
|
// ! writeln(e.Name^, ':', e.Value^);
|
|
// ! // output a:1 b:2 c:3
|
|
function Fields: TDocVariantFieldsEnumerator;
|
|
/// an enumerator able to compile "for .. in dv.FieldNames do" for objects
|
|
// - returns pointers over all Names[]
|
|
// - don't iterate if the document is an array - so n is never nil:
|
|
// ! var n: PRawUtf8;
|
|
// ! ...
|
|
// ! dv.InitJson('{a:1,b:2,c:3}');
|
|
// ! for n in dv.FieldNames do
|
|
// ! writeln(n^);
|
|
// ! // output a b c
|
|
function FieldNames: TDocVariantFieldNamesEnumerator;
|
|
/// an enumerator able to compile "for .. in dv.FieldValues do" for objects
|
|
// - returns pointers over all Values[]
|
|
// - don't iterate if the document is an array:
|
|
// ! var v: PVariant;
|
|
// ! ...
|
|
// ! dv.InitJson('{a:1,b:2,c:3}');
|
|
// ! for v in dv.FieldValues do
|
|
// ! writeln(v^);
|
|
// ! // output 1 2 3
|
|
function FieldValues: TDocVariantItemsEnumerator;
|
|
/// an enumerator able to compile "for .. in dv.Items do" for arrays
|
|
// - returns a PVariant over all Values[] of a document array
|
|
// - don't iterate if the document is an object
|
|
// - for instance:
|
|
// ! var v: PVariant;
|
|
// ! ...
|
|
// ! dv.InitArray([1, 3, 3, 4]);
|
|
// ! for v in dv.Items do
|
|
// ! writeln(v^);
|
|
// ! // output 1 2 3 4
|
|
function Items: TDocVariantItemsEnumerator;
|
|
/// an enumerator able to compile "for .. dv.Objects do" for array of objects
|
|
// - returns all Values[] of a document array which are a TDocVariantData
|
|
// - don't iterate if the document is an object, or if an item is not a
|
|
// TDocVariantData:
|
|
// ! var d: PDocVariantData;
|
|
// ! ...
|
|
// ! dv.InitJson('[{a:1,b:1},1,"no object",{a:2,b:2}]');
|
|
// ! for d in dv.Objects do
|
|
// ! writeln(d^.ToJson);
|
|
// ! // output {"a":1,"b":1} and {"a":2,"b":2} only
|
|
// ! // (ignoring 1 and "no object" items)
|
|
function Objects: TDocVariantObjectsEnumerator;
|
|
{$endif HASITERATORS}
|
|
|
|
/// save a document as UTF-8 encoded JSON
|
|
// - will write either a JSON object or array, depending of the internal
|
|
// layout of this instance (i.e. Kind property value)
|
|
// - will write 'null' if Kind is dvUndefined
|
|
// - implemented as just a wrapper around DocVariantType.ToJson()
|
|
function ToJson: RawUtf8; overload;
|
|
/// save a document as UTF-8 encoded JSON
|
|
function ToJson(const Prefix, Suffix: RawUtf8;
|
|
Format: TTextWriterJsonFormat): RawUtf8; overload;
|
|
/// save a document as UTF-8 encoded JSON file
|
|
// - you may then use InitJsonFromFile() to load and parse this file
|
|
procedure SaveToJsonFile(const FileName: TFileName);
|
|
/// save an array of objects as UTF-8 encoded non expanded layout JSON
|
|
// - returned content would be a JSON object in mORMot's TOrmTableJson non
|
|
// expanded format, with reduced JSON size, i.e.
|
|
// $ {"fieldCount":2,"values":["f1","f2","1v1",1v2,"2v1",2v2...],"rowCount":20}
|
|
// - will write '' if Kind is dvUndefined or dvObject
|
|
// - will raise an exception if the array document is not an array of
|
|
// objects with identical field names
|
|
// - can be unserialized using the InitArrayFromResults() method
|
|
function ToNonExpandedJson: RawUtf8;
|
|
/// save a document as an array of UTF-8 encoded JSON
|
|
// - will expect the document to be a dvArray - otherwise, will raise a
|
|
// EDocVariant exception
|
|
// - will use VariantToUtf8() to populate the result array: as a consequence,
|
|
// any nested custom variant types (e.g. TDocVariant) will be stored as JSON
|
|
procedure ToRawUtf8DynArray(out Result: TRawUtf8DynArray); overload;
|
|
/// save a document as an array of UTF-8 encoded JSON
|
|
// - will expect the document to be a dvArray - otherwise, will raise a
|
|
// EDocVariant exception
|
|
// - will use VariantToUtf8() to populate the result array: as a consequence,
|
|
// any nested custom variant types (e.g. TDocVariant) will be stored as JSON
|
|
function ToRawUtf8DynArray: TRawUtf8DynArray; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// save a document as an CSV of UTF-8 encoded JSON
|
|
// - will expect the document to be a dvArray - otherwise, will raise a
|
|
// EDocVariant exception
|
|
// - will use VariantToUtf8() to populate the result array: as a consequence,
|
|
// any nested custom variant types (e.g. TDocVariant) will be stored as JSON
|
|
function ToCsv(const Separator: RawUtf8 = ','): RawUtf8;
|
|
/// save a document as UTF-8 encoded Name=Value pairs
|
|
// - will follow by default the .INI format, but you can specify your
|
|
// own expected layout
|
|
procedure ToTextPairsVar(out result: RawUtf8;
|
|
const NameValueSep: RawUtf8 = '='; const ItemSep: RawUtf8 = #13#10;
|
|
Escape: TTextWriterKind = twJsonEscape);
|
|
/// save a document as UTF-8 encoded Name=Value pairs
|
|
// - will follow by default the .INI format, but you can specify your
|
|
// own expected layout
|
|
function ToTextPairs(const NameValueSep: RawUtf8 = '=';
|
|
const ItemSep: RawUtf8 = #13#10;
|
|
Escape: TTextWriterKind = twJsonEscape): RawUtf8;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// save an array document as an array of TVarRec, i.e. an array of const
|
|
// - will expect the document to be a dvArray - otherwise, will raise a
|
|
// EDocVariant exception
|
|
// - values will be passed by referenced as vtVariant to @VValue[ndx]
|
|
// - would allow to write code as such:
|
|
// ! Doc.InitArray(['one',2,3]);
|
|
// ! Doc.ToArrayOfConst(vr);
|
|
// ! s := FormatUtf8('[%,%,%]',vr,[],true);
|
|
// ! // here s='[one,2,3]') since % would be replaced by Args[] parameters
|
|
// ! s := FormatUtf8('[?,?,?]',[],vr,true);
|
|
// ! // here s='["one",2,3]') since ? would be escaped by Params[] parameters
|
|
procedure ToArrayOfConst(out Result: TTVarRecDynArray); overload;
|
|
/// save an array document as an array of TVarRec, i.e. an array of const
|
|
// - will expect the document to be a dvArray - otherwise, will raise a
|
|
// EDocVariant exception
|
|
// - values will be passed by referenced as vtVariant to @VValue[ndx]
|
|
// - would allow to write code as such:
|
|
// ! Doc.InitArray(['one',2,3]);
|
|
// ! s := FormatUtf8('[%,%,%]',Doc.ToArrayOfConst,[],true);
|
|
// ! // here s='[one,2,3]') since % would be replaced by Args[] parameters
|
|
// ! s := FormatUtf8('[?,?,?]',[],Doc.ToArrayOfConst,true);
|
|
// ! // here s='["one",2,3]') since ? would be escaped by Params[] parameters
|
|
function ToArrayOfConst: TTVarRecDynArray; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// save an object document as an URI-encoded list of parameters
|
|
// - object field names should be plain ASCII-7 RFC compatible identifiers
|
|
// (0..9a..zA..Z_.~), otherwise their values are skipped
|
|
function ToUrlEncode(const UriRoot: RawUtf8): RawUtf8;
|
|
|
|
/// returns true if this is not a true TDocVariant, or Count equals 0
|
|
function IsVoid: boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// search if a given Name do exists in this document
|
|
// - just a wrapper around GetValueIndex(aName) >= 0
|
|
function Exists(const aName: RawUtf8): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// find an item index in this document from its name
|
|
// - search will follow dvoNameCaseSensitive option of this document
|
|
// - lookup the value by name for an object document, or accept an integer
|
|
// text as index for an array document
|
|
// - returns -1 if not found
|
|
function GetValueIndex(const aName: RawUtf8): integer; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// find an item index in this document from its name
|
|
// - lookup the value by name for an object document, or accept an integer
|
|
// text as index for an array document
|
|
// - returns -1 if not found
|
|
function GetValueIndex(aName: PUtf8Char; aNameLen: PtrInt;
|
|
aCaseSensitive: boolean): integer; overload;
|
|
/// find an item in this document, and returns its value
|
|
// - raise an EDocVariant if not found and dvoReturnNullForUnknownProperty
|
|
// is not set in Options (in this case, it will return Null)
|
|
function GetValueOrRaiseException(const aName: RawUtf8): variant;
|
|
/// find an item in this document, and returns its value
|
|
// - return the supplied default if aName is not found, or if the instance
|
|
// is not a TDocVariant
|
|
function GetValueOrDefault(const aName: RawUtf8;
|
|
const aDefault: variant): variant;
|
|
/// find an item in this document, and returns its value
|
|
// - return null if aName is not found, or if the instance is not a TDocVariant
|
|
function GetValueOrNull(const aName: RawUtf8): variant;
|
|
/// find an item in this document, and returns its value
|
|
// - return a cleared variant if aName is not found, or if the instance is
|
|
// not a TDocVariant
|
|
function GetValueOrEmpty(const aName: RawUtf8): variant;
|
|
/// find an item in this document, and returns its value as enumerate
|
|
// - return false if aName is not found, if the instance is not a TDocVariant,
|
|
// or if the value is not a string corresponding to the supplied enumerate
|
|
// - return true if the name has been found, and aValue stores the value
|
|
// - will call Delete() on the found entry, if aDeleteFoundEntry is true
|
|
function GetValueEnumerate(const aName: RawUtf8; aTypeInfo: PRttiInfo;
|
|
out aValue; aDeleteFoundEntry: boolean = false): boolean;
|
|
/// returns a JSON object containing all properties matching the
|
|
// first characters of the supplied property name
|
|
// - returns null if the document is not a dvObject
|
|
// - will use IdemPChar(), so search would be case-insensitive
|
|
function GetJsonByStartName(const aStartName: RawUtf8): RawUtf8;
|
|
/// find an item in this document, and returns its value as TVarData
|
|
// - return false if aName is not found, or if the instance is not a TDocVariant
|
|
// - return true and set aValue if the name has been found
|
|
// - will use simple loop lookup to identify the name, unless aSortedCompare is
|
|
// set, and would let use a faster O(log(n)) binary search after a SortByName()
|
|
function GetVarData(const aName: RawUtf8; var aValue: TVarData;
|
|
aSortedCompare: TUtf8Compare = nil): boolean; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// find an item in this document, and returns its value as TVarData pointer
|
|
// - return nil if aName is not found, or if the instance is not a TDocVariant
|
|
// - return a pointer to the value if the name has been found, and optionally
|
|
// fill aFoundIndex^ with its index in Values[]
|
|
// - after a SortByName(aSortedCompare), could use faster binary search
|
|
function GetVarData(const aName: RawUtf8; aSortedCompare: TUtf8Compare = nil;
|
|
aFoundIndex: PInteger = nil): PVarData; overload;
|
|
/// find an item in this document, and returns its value as boolean
|
|
// - return false if aName is not found, or if the instance is not a TDocVariant
|
|
// - return true if the name has been found, and aValue stores the value
|
|
// - after a SortByName(aSortedCompare), could use faster binary search
|
|
// - consider using B[] property if you want simple read/write typed access
|
|
function GetAsBoolean(const aName: RawUtf8; out aValue: boolean;
|
|
aSortedCompare: TUtf8Compare = nil): boolean;
|
|
/// find an item in this document, and returns its value as integer
|
|
// - return false if aName is not found, or if the instance is not a TDocVariant
|
|
// - return true if the name has been found, and aValue stores the value
|
|
// - after a SortByName(aSortedCompare), could use faster binary search
|
|
// - consider using I[] property if you want simple read/write typed access
|
|
function GetAsInteger(const aName: RawUtf8; out aValue: integer;
|
|
aSortedCompare: TUtf8Compare = nil): boolean;
|
|
/// find an item in this document, and returns its value as integer
|
|
// - return false if aName is not found, or if the instance is not a TDocVariant
|
|
// - return true if the name has been found, and aValue stores the value
|
|
// - after a SortByName(aSortedCompare), could use faster binary search
|
|
// - consider using I[] property if you want simple read/write typed access
|
|
function GetAsInt64(const aName: RawUtf8; out aValue: Int64;
|
|
aSortedCompare: TUtf8Compare = nil): boolean;
|
|
/// find an item in this document, and returns its value as floating point
|
|
// - return false if aName is not found, or if the instance is not a TDocVariant
|
|
// - return true if the name has been found, and aValue stores the value
|
|
// - after a SortByName(aSortedCompare), could use faster binary search
|
|
// - consider using D[] property if you want simple read/write typed access
|
|
function GetAsDouble(const aName: RawUtf8; out aValue: double;
|
|
aSortedCompare: TUtf8Compare = nil): boolean;
|
|
/// find an item in this document, and returns its value as RawUtf8
|
|
// - return false if aName is not found, or if the instance is not a TDocVariant
|
|
// - return true if the name has been found, and aValue stores the value
|
|
// - after a SortByName(aSortedCompare), could use faster binary search
|
|
// - consider using U[] property if you want simple read/write typed access
|
|
function GetAsRawUtf8(const aName: RawUtf8; out aValue: RawUtf8;
|
|
aSortedCompare: TUtf8Compare = nil): boolean;
|
|
/// find an item in this document, and returns its value as a TDocVariantData
|
|
// - return false if aName is not found, or if the instance is not a TDocVariant
|
|
// - return true if the name has been found and points to a TDocVariant:
|
|
// then aValue stores a pointer to the value
|
|
// - after a SortByName(aSortedCompare), could use faster binary search
|
|
function GetAsDocVariant(const aName: RawUtf8; out aValue: PDocVariantData;
|
|
aSortedCompare: TUtf8Compare = nil): boolean;
|
|
/// find a non-void array item in this document, and returns its value
|
|
// - return false if aName is not found, or if not a TDocVariant array
|
|
// - return true if the name was found as non-void array and set to aArray
|
|
// - after a SortByName(aSortedCompare), could use faster binary search
|
|
function GetAsArray(const aName: RawUtf8; out aArray: PDocVariantData;
|
|
aSortedCompare: TUtf8Compare = nil): boolean;
|
|
/// find a non-void object item in this document, and returns its value
|
|
// - return false if aName is not found, or if not a TDocVariant object
|
|
// - return true if the name was found as non-void object and set to aObject
|
|
// - after a SortByName(aSortedCompare), could use faster binary search
|
|
function GetAsObject(const aName: RawUtf8; out aObject: PDocVariantData;
|
|
aSortedCompare: TUtf8Compare = nil): boolean;
|
|
/// find an item in this document, and returns its value as a TDocVariantData
|
|
// - returns a void TDocVariant if aName is not a document
|
|
// - after a SortByName(aSortedCompare), could use faster binary search
|
|
// - consider using O[] or A[] properties if you want simple read-only
|
|
// access, or O_[] or A_[] properties if you want the ability to add
|
|
// a missing object or array in the document
|
|
function GetAsDocVariantSafe(const aName: RawUtf8;
|
|
aSortedCompare: TUtf8Compare = nil): PDocVariantData;
|
|
/// find an item in this document, and returns pointer to its value
|
|
// - return false if aName is not found
|
|
// - return true if the name has been found: then aValue stores a pointer
|
|
// to the value
|
|
// - after a SortByName(aSortedCompare), could use faster binary search
|
|
function GetAsPVariant(const aName: RawUtf8; out aValue: PVariant;
|
|
aSortedCompare: TUtf8Compare = nil): boolean; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// find an item in this document, and returns pointer to its value
|
|
// - lookup the value by aName/aNameLen for an object document, or accept
|
|
// an integer text as index for an array document
|
|
// - return nil if aName is not found, or if the instance is not a TDocVariant
|
|
// - return a pointer to the stored variant, if the name has been found
|
|
function GetAsPVariant(aName: PUtf8Char; aNameLen: PtrInt): PVariant; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
/// retrieve a value, given its path
|
|
// - path is defined as a dotted name-space, e.g. 'doc.glossary.title'
|
|
// - return Unassigned (varEmpty) if there is no item at the supplied aPath
|
|
// - you can set e.g. aPathDelim = '/' to search e.g. for 'parent/child'
|
|
// - see also the P[] property if the default aPathDelim = '.' is enough
|
|
function GetValueByPath(
|
|
const aPath: RawUtf8; aPathDelim: AnsiChar = '.'): variant; overload;
|
|
/// retrieve a value, given its path
|
|
// - path is defined as a dotted name-space, e.g. 'doc.glossary.title'
|
|
// - returns FALSE if there is no item at the supplied aPath
|
|
// - returns TRUE and set the found value in aValue
|
|
// - you can set e.g. aPathDelim = '/' to search e.g. for 'parent/child'
|
|
// - see also the P[] property if the default aPathDelim = '.' is enough
|
|
function GetValueByPath(const aPath: RawUtf8; out aValue: variant;
|
|
aPathDelim: AnsiChar = '.'): boolean; overload;
|
|
/// retrieve a value, given its path
|
|
// - path is defined as a list of names, e.g. ['doc','glossary','title']
|
|
// - return Unassigned (varEmpty) if there is no item at the supplied aPath
|
|
// - this method will only handle nested TDocVariant values: use the
|
|
// slightly slower GetValueByPath() overloaded method, if any nested object
|
|
// may be of another type (e.g. a TBsonVariant)
|
|
function GetValueByPath(
|
|
const aDocVariantPath: array of RawUtf8): variant; overload;
|
|
/// retrieve a reference to a value, given its path
|
|
// - path is defined as a dotted name-space, e.g. 'doc.glossary.title'
|
|
// - if the supplied aPath does not match any object, it will return nil
|
|
// - if aPath is found, returns a pointer to the corresponding value
|
|
// - you can set e.g. aPathDelim = '/' to search e.g. for 'parent/child'
|
|
function GetPVariantByPath(const aPath: RawUtf8;
|
|
aPathDelim: AnsiChar = '.'): PVariant;
|
|
/// retrieve a reference to a value, given its path
|
|
// - if the supplied aPath does not match any object, it will follow
|
|
// dvoReturnNullForUnknownProperty option
|
|
function GetPVariantExistingByPath(const aPath: RawUtf8;
|
|
aPathDelim: AnsiChar = '.'): PVariant;
|
|
/// retrieve a reference to a TDocVariant, given its path
|
|
// - path is defined as a dotted name-space, e.g. 'doc.glossary.title'
|
|
// - if the supplied aPath does not match any object, it will return false
|
|
// - if aPath stores a valid TDocVariant, returns true and a pointer to it
|
|
// - you can set e.g. aPathDelim = '/' to search e.g. for 'parent/child'
|
|
function GetDocVariantByPath(const aPath: RawUtf8;
|
|
out aValue: PDocVariantData; aPathDelim: AnsiChar = '.'): boolean;
|
|
/// retrieve a dvObject in the dvArray, from a property value
|
|
// - {aPropName:aPropValue} will be searched within the stored array,
|
|
// and the corresponding item will be copied into Dest, on match
|
|
// - returns FALSE if no match is found, TRUE if found and copied
|
|
// - create a copy of the variant by default, unless DestByRef is TRUE
|
|
// - will call VariantEquals() for value comparison
|
|
function GetItemByProp(const aPropName, aPropValue: RawUtf8;
|
|
aPropValueCaseSensitive: boolean; var Dest: variant;
|
|
DestByRef: boolean = false): boolean;
|
|
/// retrieve a reference to a dvObject in the dvArray, from a property value
|
|
// - {aPropName:aPropValue} will be searched within the stored array,
|
|
// and the corresponding item will be copied into Dest, on match
|
|
// - returns FALSE if no match is found, TRUE if found and copied by reference
|
|
function GetDocVariantByProp(const aPropName, aPropValue: RawUtf8;
|
|
aPropValueCaseSensitive: boolean; out Dest: PDocVariantData): boolean;
|
|
/// find an item in this document, and returns its value
|
|
// - raise an EDocVariant if not found and dvoReturnNullForUnknownProperty
|
|
// is not set in Options (in this case, it will return Null)
|
|
// - create a copy of the variant by default, unless DestByRef is TRUE
|
|
function RetrieveValueOrRaiseException(aName: PUtf8Char; aNameLen: integer;
|
|
aCaseSensitive: boolean; var Dest: variant; DestByRef: boolean): boolean; overload;
|
|
/// retrieve an item in this document from its index, and returns its value
|
|
// - raise an EDocVariant if the supplied Index is not in the 0..Count-1
|
|
// range and dvoReturnNullForUnknownProperty is set in Options
|
|
// - create a copy of the variant by default, unless DestByRef is TRUE
|
|
procedure RetrieveValueOrRaiseException(Index: integer;
|
|
var Dest: variant; DestByRef: boolean); overload;
|
|
/// retrieve an item in this document from its index, and returns its Name
|
|
// - raise an EDocVariant if the supplied Index is not in the 0..Count-1
|
|
// range and dvoReturnNullForUnknownProperty is set in Options
|
|
procedure RetrieveNameOrRaiseException(Index: integer; var Dest: RawUtf8);
|
|
/// returns a TDocVariant object containing all properties matching the
|
|
// first characters of the supplied property name
|
|
// - returns null if the document is not a dvObject
|
|
// - will use IdemPChar(), so search would be case-insensitive
|
|
function GetValuesByStartName(const aStartName: RawUtf8;
|
|
TrimLeftStartName: boolean = false): variant;
|
|
/// set an item in this document from its index
|
|
// - raise an EDocVariant if the supplied Index is not in 0..Count-1 range
|
|
procedure SetValueOrRaiseException(Index: integer; const NewValue: variant);
|
|
/// set a value, given its path
|
|
// - path is defined as a dotted name-space, e.g. 'doc.glossary.title'
|
|
// - aCreateIfNotExisting=true will force missing nested objects creation
|
|
// - returns FALSE if there is no item to be set at the supplied aPath
|
|
// - returns TRUE and set the found value in aValue
|
|
// - you can set e.g. aPathDelim = '/' to search e.g. for 'parent/child'
|
|
function SetValueByPath(const aPath: RawUtf8; const aValue: variant;
|
|
aCreateIfNotExisting: boolean = false; aPathDelim: AnsiChar = '.'): boolean;
|
|
|
|
/// add a value in this document
|
|
// - if aName is set, if dvoCheckForDuplicatedNames option is set, any
|
|
// existing duplicated aName will raise an EDocVariant; if instance's
|
|
// kind is dvArray and aName is defined, it will raise an EDocVariant
|
|
// - aName may be '' e.g. if you want to store an array: in this case,
|
|
// dvoCheckForDuplicatedNames option should not be set; if instance's Kind
|
|
// is dvObject, it will raise an EDocVariant exception
|
|
// - if aValueOwned is true, then the supplied aValue will be assigned to
|
|
// the internal values - by default, it will use SetVariantByValue()
|
|
// - you can therefore write e.g.:
|
|
// ! TDocVariant.New(aVariant);
|
|
// ! Assert(TDocVariantData(aVariant).Kind=dvUndefined);
|
|
// ! TDocVariantData(aVariant).AddValue('name','John');
|
|
// ! Assert(TDocVariantData(aVariant).Kind=dvObject);
|
|
// - you can specify an optional index in the array where to insert
|
|
// - returns the index of the corresponding newly added value
|
|
function AddValue(const aName: RawUtf8; const aValue: variant;
|
|
aValueOwned: boolean = false; aIndex: integer = -1): integer; overload;
|
|
/// add a value in this document
|
|
// - overloaded function accepting a UTF-8 encoded buffer for the name
|
|
function AddValue(aName: PUtf8Char; aNameLen: integer; const aValue: variant;
|
|
aValueOwned: boolean = false; aIndex: integer = -1): integer; overload;
|
|
/// add a value in this document, or update an existing entry
|
|
// - if instance's Kind is dvArray, it will raise an EDocVariant exception
|
|
// - any existing Name would be updated with the new Value, unless
|
|
// OnlyAddMissing is set to TRUE, in which case existing values would remain
|
|
// - returns the index of the corresponding value, which may be just added
|
|
function AddOrUpdateValue(const aName: RawUtf8; const aValue: variant;
|
|
wasAdded: PBoolean = nil; OnlyAddMissing: boolean = false): integer;
|
|
/// add a value in this document, from its text representation
|
|
// - this function expects a UTF-8 text for the value, which would be
|
|
// converted to a variant number, if possible (as varInt/varInt64/varCurrency
|
|
// and/or as varDouble is dvoAllowDoubleValue option is set)
|
|
// - if Update=TRUE, will set the property, even if it is existing
|
|
function AddValueFromText(const aName, aValue: RawUtf8;
|
|
DoUpdate: boolean = false): integer;
|
|
/// add some properties to a TDocVariantData dvObject
|
|
// - data is supplied two by two, as Name,Value pairs
|
|
// - caller should ensure that Kind=dvObject, otherwise it won't do anything
|
|
// - any existing Name would be duplicated - use Update() if you want to
|
|
// replace any existing value
|
|
procedure AddNameValuesToObject(const NameValuePairs: array of const);
|
|
/// merge some properties to a TDocVariantData dvObject
|
|
// - data is supplied two by two, as Name,Value pairs
|
|
// - caller should ensure that Kind=dvObject, otherwise it won't do anything
|
|
// - any existing Name would be updated with the new Value
|
|
procedure Update(const NameValuePairs: array of const);
|
|
{$ifndef PUREMORMOT2}
|
|
/// deprecated method which redirects to Update()
|
|
procedure AddOrUpdateNameValuesToObject(const NameValuePairs: array of const);
|
|
{$endif PUREMORMOT2}
|
|
/// merge some TDocVariantData dvObject properties to a TDocVariantData dvObject
|
|
// - data is supplied two by two, as Name,Value pairs
|
|
// - caller should ensure that both variants have Kind=dvObject, otherwise
|
|
// it won't do anything
|
|
// - any existing Name would be updated with the new Value, unless
|
|
// OnlyAddMissing is set to TRUE, in which case existing values would remain
|
|
procedure AddOrUpdateObject(const NewValues: variant;
|
|
OnlyAddMissing: boolean = false; RecursiveUpdate: boolean = false);
|
|
/// add a value to this document, handled as array
|
|
// - if instance's Kind is dvObject, it will raise an EDocVariant exception
|
|
// - you can therefore write e.g.:
|
|
// ! TDocVariant.New(aVariant);
|
|
// ! Assert(TDocVariantData(aVariant).Kind=dvUndefined);
|
|
// ! TDocVariantData(aVariant).AddItem('one');
|
|
// ! Assert(TDocVariantData(aVariant).Kind=dvArray);
|
|
// - you can specify an optional index in the array where to insert
|
|
// - returns the index of the corresponding newly added item
|
|
function AddItem(const aValue: variant; aIndex: integer = -1): integer; overload;
|
|
/// add a TDocVariant value to this document, handled as array
|
|
function AddItem(const aValue: TDocVariantData; aIndex: integer = -1): integer; overload;
|
|
/// add a value to this document, handled as array, from its text representation
|
|
// - this function expects a UTF-8 text for the value, which would be
|
|
// converted to a variant number, if possible (as varInt/varInt64/varCurrency
|
|
// and/or as varDouble is dvoAllowDoubleValue option is set)
|
|
// - if instance's Kind is dvObject, it will raise an EDocVariant exception
|
|
// - you can specify an optional index in the array where to insert
|
|
// - returns the index of the corresponding newly added item
|
|
function AddItemFromText(const aValue: RawUtf8;
|
|
aIndex: integer = -1): integer;
|
|
/// add a RawUtf8 value to this document, handled as array
|
|
// - if instance's Kind is dvObject, it will raise an EDocVariant exception
|
|
// - you can specify an optional index in the array where to insert
|
|
// - returns the index of the corresponding newly added item
|
|
function AddItemText(const aValue: RawUtf8; aIndex: integer = -1): integer;
|
|
/// add one or several values to this document, handled as array
|
|
// - if instance's Kind is dvObject, it will raise an EDocVariant exception
|
|
procedure AddItems(const aValue: array of const);
|
|
/// add one object document to this document
|
|
// - if the document is an array, keep aName=''
|
|
// - if the document is an object, set the new object property as aName
|
|
// - new object will keep the same options as this document
|
|
// - slightly faster than AddItem(_Obj(...)) or AddValue(aName, _Obj(...))
|
|
procedure AddObject(const aNameValuePairs: array of const;
|
|
const aName: RawUtf8 = '');
|
|
/// add one or several values from another document
|
|
// - supplied document should be of the same kind than the current one,
|
|
// otherwise nothing is added
|
|
// - for an object, dvoCheckForDuplicatedNames flag is used: use
|
|
// AddOrUpdateFrom() to force objects merging
|
|
procedure AddFrom(const aDocVariant: Variant);
|
|
/// merge (i.e. add or update) several values from another object
|
|
// - current document should be an object
|
|
procedure AddOrUpdateFrom(const aDocVariant: Variant;
|
|
aOnlyAddMissing: boolean = false);
|
|
/// add one or several properties, specified by path, from another object
|
|
// - path are defined as open array, e.g. ['doc','glossary','title'], but
|
|
// could also contained nested paths, e.g. ['doc.glossary', title'] or
|
|
// ['doc', 'glossary/title'] of aPathDelim is '/'
|
|
// - matching values would be added as root values, with the path as name
|
|
// - instance and supplied aSource should be a dvObject
|
|
procedure AddByPath(const aSource: TDocVariantData;
|
|
const aPaths: array of RawUtf8; aPathDelim: AnsiChar = '.');
|
|
/// delete a value/item in this document, from its index
|
|
// - return TRUE on success, FALSE if the supplied index is not correct
|
|
function Delete(Index: PtrInt): boolean; overload;
|
|
/// delete a value/item in this document, from its name
|
|
// - return TRUE on success, FALSE if the supplied name does not exist
|
|
function Delete(const aName: RawUtf8; aValue: PVariant = nil): boolean; overload;
|
|
/// delete/filter some values/items in this document, from their name
|
|
// - return the number of deleted items
|
|
function Delete(const aNames: array of RawUtf8): integer; overload;
|
|
/// delete a value/item in this document, from its name
|
|
// - path is defined as a dotted name-space, e.g. 'doc.glossary.title'
|
|
// - return TRUE on success, FALSE if the supplied name does not exist
|
|
// - you can set e.g. aPathDelim = '/' to search e.g. for 'parent/child'
|
|
function DeleteByPath(const aPath: RawUtf8; aPathDelim: AnsiChar = '.';
|
|
aDeletedValue: PVariant = nil): boolean;
|
|
/// delete a value in this document, by property name match
|
|
// - {aPropName:aPropValue} will be searched within the stored array or
|
|
// object, and the corresponding item will be deleted, on match
|
|
// - returns FALSE if no match is found, TRUE if found and deleted
|
|
// - will call VariantEquals() for value comparison
|
|
function DeleteByProp(const aPropName, aPropValue: RawUtf8;
|
|
aPropValueCaseSensitive: boolean): boolean;
|
|
/// delete one or several value/item in this document, from its value
|
|
// - returns the number of deleted items
|
|
// - returns 0 if the document is not a dvObject, or if no match was found
|
|
// - if the value exists several times, all occurrences would be removed
|
|
// - is optimized for DeleteByValue(null) call
|
|
function DeleteByValue(const aValue: Variant;
|
|
CaseInsensitive: boolean = false): integer;
|
|
/// delete all values matching the first characters of a property name
|
|
// - returns the number of deleted items
|
|
// - returns 0 if the document is not a dvObject, or if no match was found
|
|
// - will use IdemPChar(), so search would be case-insensitive
|
|
function DeleteByStartName(aStartName: PUtf8Char;
|
|
aStartNameLen: integer): integer;
|
|
/// retrieve a value at a given index, and delete it from the array
|
|
// - negative aIndex are from VCount, i.e. Extract(-1) pop the last item
|
|
function Extract(aIndex: integer; var aValue: variant;
|
|
aName: PRawUtf8 = nil): boolean;
|
|
/// search a property match in this document, handled as array or object
|
|
// - {aPropName:aPropValue} will be searched within the stored array or
|
|
// object, and the corresponding item index will be returned, on match
|
|
// - returns -1 if no match is found
|
|
// - will call VariantEquals() for value comparison
|
|
function SearchItemByProp(const aPropName, aPropValue: RawUtf8;
|
|
aPropValueCaseSensitive: boolean): integer; overload;
|
|
/// search a property match in this document, handled as array or object
|
|
// - {aPropName:aPropValue} will be searched within the stored array or
|
|
// object, and the corresponding item index will be returned, on match
|
|
// - returns -1 if no match is found
|
|
// - will call VariantEquals() for value comparison
|
|
function SearchItemByProp(const aPropNameFmt: RawUtf8;
|
|
const aPropNameArgs: array of const; const aPropValue: RawUtf8;
|
|
aPropValueCaseSensitive: boolean): integer; overload;
|
|
/// search a value in this document, handled as array
|
|
// - aValue will be searched within the stored array
|
|
// and the corresponding item index will be returned, on match
|
|
// - returns -1 if no match is found
|
|
// - you could make several searches, using the StartIndex optional parameter
|
|
function SearchItemByValue(const aValue: Variant;
|
|
CaseInsensitive: boolean = false; StartIndex: PtrInt = 0): PtrInt;
|
|
/// search and count occurences of one value in this document, handled as array
|
|
function CountItemByValue(const aValue: Variant;
|
|
CaseInsensitive: boolean = false; StartIndex: integer = 0): integer;
|
|
/// sort the document object values by name
|
|
// - do nothing if the document is not a dvObject
|
|
// - will follow case-insensitive order (@StrIComp) by default, but you
|
|
// can specify @StrComp as comparer function for case-sensitive ordering
|
|
// - once sorted, you can use GetVarData(..,Compare) or GetAs*(..,Compare)
|
|
// methods for much faster O(log(n)) binary search
|
|
procedure SortByName(SortCompare: TUtf8Compare = nil;
|
|
SortCompareReversed: boolean = false);
|
|
/// sort the document object values by value using a comparison function
|
|
// - work for both dvObject and dvArray documents
|
|
// - will sort by UTF-8 text (VariantCompare) if no custom aCompare is supplied
|
|
procedure SortByValue(SortCompare: TVariantCompare = nil;
|
|
SortCompareReversed: boolean = false);
|
|
/// sort the document object values by value using a comparison method
|
|
// - work for both dvObject and dvArray documents
|
|
// - you should supply a TVariantComparer callback method
|
|
procedure SortByRow(const SortComparer: TVariantComparer;
|
|
SortComparerReversed: boolean = false);
|
|
/// sort the document array values by a field of some stored objet values
|
|
// - do nothing if the document is not a dvArray, or if the items are no dvObject
|
|
// - aValueCompare will be called with the aItemPropName values, not row
|
|
// - will sort by UTF-8 text (VariantCompare) if no custom aValueCompare is supplied
|
|
// - this method is faster than SortByValue/SortByRow
|
|
procedure SortArrayByField(const aItemPropName: RawUtf8;
|
|
aValueCompare: TVariantCompare = nil;
|
|
aValueCompareReverse: boolean = false;
|
|
aNameSortedCompare: TUtf8Compare = nil);
|
|
/// sort the document array values by field(s) of some stored objet values
|
|
// - allow up to 4 fields (aItemPropNames[0]..aItemPropNames[3])
|
|
// - do nothing if the document is not a dvArray, or if the items are no dvObject
|
|
// - will sort by UTF-8 text (VariantCompare) if no aValueCompareField is supplied
|
|
procedure SortArrayByFields(const aItemPropNames: array of RawUtf8;
|
|
aValueCompare: TVariantCompare = nil;
|
|
const aValueCompareField: TVariantCompareField = nil;
|
|
aValueCompareReverse: boolean = false; aNameSortedCompare: TUtf8Compare = nil);
|
|
/// inverse the order of Names and Values of this document
|
|
// - could be applied after a content sort if needed
|
|
procedure Reverse;
|
|
/// create a TDocVariant object, from a selection of properties of the
|
|
// objects of this document array, by property name
|
|
// - if the document is a dvObject, to reduction will be applied to all
|
|
// its properties
|
|
// - if the document is a dvArray, the reduction will be applied to each
|
|
// stored item, if it is a document
|
|
procedure Reduce(const aPropNames: array of RawUtf8; aCaseSensitive: boolean;
|
|
var result: TDocVariantData; aDoNotAddVoidProp: boolean = false); overload;
|
|
/// create a TDocVariant object, from a selection of properties of the
|
|
// objects of this document array, by property name
|
|
// - always returns a TDocVariantData, even if no property name did match
|
|
// (in this case, it is dvUndefined)
|
|
function Reduce(const aPropNames: array of RawUtf8; aCaseSensitive: boolean;
|
|
aDoNotAddVoidProp: boolean = false): variant; overload;
|
|
/// create a TDocVariant array, matching a filtering expression
|
|
// - expressions are e.g. 'name=Synopse' or 'price<100'
|
|
procedure ReduceFilter(const aExpression: RawUtf8; var result: TDocVariantData;
|
|
aLimit: integer = 0; aCompare: TVariantCompare = nil); overload;
|
|
/// create a TDocVariant array, matching a filtering expression
|
|
// - expressions are e.g. 'name=Synopse' or 'price<100'
|
|
function ReduceFilter(const aExpression: RawUtf8; aLimit: integer = 0): variant; overload;
|
|
/// create a TDocVariant array, matching a filtering expression
|
|
// - e.g. ReduceFilter('name=','Synopse') or ReduceFilter('price<',MaxPrice)
|
|
procedure ReduceFilter(const aExpression: RawUtf8; const aValue: variant;
|
|
var result: TDocVariantData; aCompare: TVariantCompare = nil;
|
|
aLimit: integer = 0); overload;
|
|
/// create a TDocVariant array, matching a filtering expression
|
|
// - e.g. ReduceFilter('name=','Synopse') or ReduceFilter('price<',MaxPrice)
|
|
function ReduceFilter(const aExpression: RawUtf8; const aValue: variant;
|
|
aLimit: integer = 0): variant; overload;
|
|
/// create a TDocVariant array, matching a filtering set of raw parameters
|
|
procedure ReduceFilter(const aKey: RawUtf8; const aValue: variant;
|
|
aMatch: TCompareOperator; aCompare: TVariantCompare; aLimit: integer;
|
|
var result: TDocVariantData); overload;
|
|
/// create a TDocVariant array, from the values of a single property of the
|
|
// objects of this document array, specified by name
|
|
// - you can optionally apply an additional filter to each reduced item
|
|
procedure ReduceAsArray(const aPropName: RawUtf8;
|
|
var result: TDocVariantData;
|
|
const OnReduce: TOnReducePerItem = nil); overload;
|
|
/// create a TDocVariant array, from the values of a single property of the
|
|
// objects of this document array, specified by name
|
|
// - always returns a TDocVariantData, even if no property name did match
|
|
// (in this case, it is dvUndefined)
|
|
// - you can optionally apply an additional filter to each reduced item
|
|
function ReduceAsArray(const aPropName: RawUtf8;
|
|
const OnReduce: TOnReducePerItem = nil): variant; overload;
|
|
/// create a TDocVariant array, from the values of a single property of the
|
|
// objects of this document array, specified by name
|
|
// - this overloaded method accepts an additional filter to each reduced item
|
|
procedure ReduceAsArray(const aPropName: RawUtf8;
|
|
var result: TDocVariantData;
|
|
const OnReduce: TOnReducePerValue); overload;
|
|
/// create a TDocVariant array, from the values of a single property of the
|
|
// objects of this document array, specified by name
|
|
// - always returns a TDocVariantData, even if no property name did match
|
|
// (in this case, it is dvUndefined)
|
|
// - this overloaded method accepts an additional filter to each reduced item
|
|
function ReduceAsArray(const aPropName: RawUtf8;
|
|
const OnReduce: TOnReducePerValue): variant; overload;
|
|
/// return the variant values of a single property of the objects of this
|
|
// document array, specified by name
|
|
// - returns nil if the document is not a dvArray
|
|
function ReduceAsVariantArray(const aPropName: RawUtf8;
|
|
aDuplicates: TSearchDuplicate = sdNone): TVariantDynArray;
|
|
/// rename some properties of a TDocVariant object
|
|
// - returns the number of property names modified
|
|
function Rename(const aFromPropName, aToPropName: TRawUtf8DynArray): integer;
|
|
/// return a dynamic array with all dvObject Names, and length() = Count
|
|
// - since length(Names) = Capacity, you can use this method to retrieve
|
|
// all the object keys
|
|
// - consider using FieldNames iterator or Names[0..Count-1] if you need
|
|
// to iterate on the key names
|
|
// - will internally force length(Names)=length(Values)=Capacity=Count and
|
|
// return the Names[] instance with no memory (re)allocation
|
|
// - if the document is not a dvObject, will return nil
|
|
function GetNames: TRawUtf8DynArray;
|
|
/// map {"obj.prop1"..,"obj.prop2":..} into {"obj":{"prop1":..,"prop2":...}}
|
|
// - the supplied aObjectPropName should match the incoming dotted value
|
|
// of all properties (e.g. 'obj' for "obj.prop1")
|
|
// - if any of the incoming property is not of "obj.prop#" form, the
|
|
// whole process would be ignored
|
|
// - return FALSE if the TDocVariant did not change
|
|
// - return TRUE if the TDocVariant has been flattened
|
|
function FlattenAsNestedObject(const aObjectPropName: RawUtf8): boolean;
|
|
|
|
/// how this document will behave
|
|
// - those options are set when creating the instance
|
|
// - dvoArray and dvoObject are not options, but define the document Kind,
|
|
// so those items are ignored when assigned to this property
|
|
property Options: TDocVariantOptions
|
|
read VOptions write SetOptions;
|
|
/// returns the document internal layout
|
|
// - just after initialization, it will return dvUndefined
|
|
// - most of the time, you will add named values with AddValue() or by
|
|
// setting the variant properties: it will return dvObject
|
|
// - but is you use AddItem(), values will have no associated names: the
|
|
// document will be a dvArray
|
|
// - is computed from the dvoArray or dvoObject flags presence in Options
|
|
property Kind: TDocVariantKind
|
|
read GetKind;
|
|
/// return the custom variant type identifier, i.e. DocVariantType.VarType
|
|
property VarType: word
|
|
read VType;
|
|
/// number of items stored in this document
|
|
// - always 0 for Kind=dvUndefined
|
|
// - the number of name/value pairs for Kind=dvObject (may be 0 if void)
|
|
// - the number of items for Kind=dvArray (may be 0 if void)
|
|
property Count: integer
|
|
read VCount;
|
|
/// the current capacity of this document
|
|
// - allow direct access to VValue[] length
|
|
property Capacity: integer
|
|
read GetCapacity write SetCapacity;
|
|
/// direct acces to the low-level internal array of values
|
|
// - note that length(Values)=Capacity and not Count, so copy(Values, 0, Count)
|
|
// or use FieldValues iterator if you want the exact count
|
|
// - transtyping a variant and direct access to TDocVariantData is the
|
|
// fastest way of accessing all properties of a given dvObject:
|
|
// ! with _Safe(aVariantObject)^ do
|
|
// ! for i := 0 to Count-1 do
|
|
// ! writeln(Names[i],'=',Values[i]);
|
|
// - or to access a dvArray items (e.g. a MongoDB collection):
|
|
// ! with TDocVariantData(aVariantArray) do
|
|
// ! for i := 0 to Count-1 do
|
|
// ! writeln(Values[i]);
|
|
property Values: TVariantDynArray
|
|
read VValue;
|
|
/// direct acces to the low-level internal array of names
|
|
// - is void (nil) if Kind is not dvObject
|
|
// - note that length(Names)=Capacity and not Count, so copy(Names, 0, Count)
|
|
// or use FieldNames iterator or GetNames if you want the exact count
|
|
// - transtyping a variant and direct access to TDocVariantData is the
|
|
// fastest way of accessing all properties of a given dvObject:
|
|
// ! with _Safe(aVariantObject)^ do
|
|
// ! for i := 0 to Count-1 do
|
|
// ! writeln(Names[i],'=',Values[i]);
|
|
property Names: TRawUtf8DynArray
|
|
read VName;
|
|
/// find an item in this document, and returns its value
|
|
// - raise an EDocVariant if aNameOrIndex is neither an integer nor a string
|
|
// - raise an EDocVariant if Kind is dvArray and aNameOrIndex is a string
|
|
// or if Kind is dvObject and aNameOrIndex is an integer
|
|
// - raise an EDocVariant if Kind is dvObject and if aNameOrIndex is a
|
|
// string, which is not found within the object property names and
|
|
// dvoReturnNullForUnknownProperty is set in Options
|
|
// - raise an EDocVariant if Kind is dvArray and if aNameOrIndex is a
|
|
// integer, which is not within 0..Count-1 and dvoReturnNullForUnknownProperty
|
|
// is set in Options
|
|
// - so you can use directly:
|
|
// ! // for an array document:
|
|
// ! aVariant := TDocVariant.NewArray(['one',2,3.0]);
|
|
// ! for i := 0 to TDocVariantData(aVariant).Count-1 do
|
|
// ! aValue := TDocVariantData(aVariant).Value[i];
|
|
// ! // for an object document:
|
|
// ! aVariant := TDocVariant.NewObject(['name','John','year',1972]);
|
|
// ! assert(aVariant.Name=TDocVariantData(aVariant)['name']);
|
|
// ! assert(aVariant.year=TDocVariantData(aVariant)['year']);
|
|
// - due to the internal implementation of variant execution (somewhat
|
|
// slow _DispInvoke() function), it is a bit faster to execute:
|
|
// ! aValue := TDocVariantData(aVariant).Value['name'];
|
|
// or
|
|
// ! aValue := _Safe(aVariant).Value['name'];
|
|
// instead of
|
|
// ! aValue := aVariant.name;
|
|
// but of course, if want to want to access the content by index (typically
|
|
// for a dvArray), using Values[] - and Names[] - properties is much faster
|
|
// than this variant-indexed pseudo-property:
|
|
// ! with TDocVariantData(aVariant) do
|
|
// ! for i := 0 to Count-1 do
|
|
// ! Writeln(Values[i]);
|
|
// is faster than:
|
|
// ! with TDocVariantData(aVariant) do
|
|
// ! for i := 0 to Count-1 do
|
|
// ! Writeln(Value[i]);
|
|
// which is faster than:
|
|
// ! for i := 0 to aVariant.Count-1 do
|
|
// ! Writeln(aVariant._(i));
|
|
// - this property will return the value as varByRef (just like with
|
|
// variant late binding of any TDocVariant instance), so you can write:
|
|
// !var
|
|
// ! Doc: TDocVariantData; // stack-allocated variable
|
|
// !begin
|
|
// ! Doc.InitJson('{arr:[1,2]}');
|
|
// ! assert(Doc.Count=2);
|
|
// ! Doc.Value['arr'].Add(3); // works since Doc.Value['arr'] is varByRef
|
|
// ! writeln(Doc.ToJson); // will write '{"arr":[1,2,3]}'
|
|
// !end;
|
|
// - if you want to access a property as a copy, i.e. to assign it to a
|
|
// variant variable which will stay alive after this TDocVariant instance
|
|
// is release, you should not use Value[] but rather
|
|
// GetValueOrRaiseException or GetValueOrNull/GetValueOrEmpty
|
|
// - see U[] I[] B[] D[] O[] O_[] A[] A_[] _[] properties for direct access
|
|
// of strong typed values, or P[] to retrieve a variant from its path
|
|
property Value[const aNameOrIndex: Variant]: Variant
|
|
read GetValueOrItem write SetValueOrItem; default;
|
|
|
|
/// direct access to a dvObject UTF-8 stored property value from its name
|
|
// - slightly faster than the variant-based Value[] default property
|
|
// - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
|
|
// - use GetAsRawUtf8() if you want to check the availability of the field
|
|
// - U['prop'] := 'value' would add a new property, or overwrite an existing
|
|
property U[const aName: RawUtf8]: RawUtf8
|
|
read GetRawUtf8ByName write SetRawUtf8ByName;
|
|
/// direct string access to a dvObject UTF-8 stored property value from its name
|
|
// - just a wrapper around U[] property, to avoid a compilation warning when
|
|
// using plain string variables (internally, RawUtf8 will be used for storage)
|
|
// - slightly faster than the variant-based Value[] default property
|
|
// - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
|
|
// - use GetAsRawUtf8() if you want to check the availability of the field
|
|
// - S['prop'] := 'value' would add a new property, or overwrite an existing
|
|
property S[const aName: RawUtf8]: string
|
|
read GetStringByName write SetStringByName;
|
|
/// direct access to a dvObject integer stored property value from its name
|
|
// - slightly faster than the variant-based Value[] default property
|
|
// - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
|
|
// - use GetAsInt/GetAsInt64 if you want to check the availability of the field
|
|
// - I['prop'] := 123 would add a new property, or overwrite an existing
|
|
property I[const aName: RawUtf8]: Int64
|
|
read GetInt64ByName write SetInt64ByName;
|
|
/// direct access to a dvObject boolean stored property value from its name
|
|
// - slightly faster than the variant-based Value[] default property
|
|
// - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
|
|
// - use GetAsBoolean if you want to check the availability of the field
|
|
// - B['prop'] := true would add a new property, or overwrite an existing
|
|
property B[const aName: RawUtf8]: boolean
|
|
read GetBooleanByName write SetBooleanByName;
|
|
/// direct access to a dvObject floating-point stored property value from its name
|
|
// - slightly faster than the variant-based Value[] default property
|
|
// - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
|
|
// - use GetAsDouble if you want to check the availability of the field
|
|
// - D['prop'] := 1.23 would add a new property, or overwrite an existing
|
|
property D[const aName: RawUtf8]: Double
|
|
read GetDoubleByName write SetDoubleByName;
|
|
/// direct access to a dvObject existing dvObject property from its name
|
|
// - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
|
|
// - O['prop'] would return a fake void TDocVariant if the property is not
|
|
// existing or not a dvObject, just like GetAsDocVariantSafe()
|
|
// - use O_['prop'] to force adding any missing property
|
|
property O[const aName: RawUtf8]: PDocVariantData
|
|
read GetObjectExistingByName;
|
|
/// direct access or add a dvObject's dvObject property from its name
|
|
// - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
|
|
// - O_['prop'] would add a new property if there is none existing, or
|
|
// overwrite an existing property which is not a dvObject
|
|
// - the new property object would inherit from the Options of this instance
|
|
property O_[const aName: RawUtf8]: PDocVariantData
|
|
read GetObjectOrAddByName;
|
|
/// direct access to a dvObject existing dvArray property from its name
|
|
// - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
|
|
// - A['prop'] would return a fake void TDocVariant if the property is not
|
|
// existing or not a dvArray, just like GetAsDocVariantSafe()
|
|
// - use A_['prop'] to force adding any missing property
|
|
property A[const aName: RawUtf8]: PDocVariantData
|
|
read GetArrayExistingByName;
|
|
/// direct access or add a dvObject's dvArray property from its name
|
|
// - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
|
|
// - A_['prop'] would add a new property if there is none existing, or
|
|
// overwrite an existing property which is not a dvArray
|
|
// - the new property array would inherit from the Options of this instance
|
|
property A_[const aName: RawUtf8]: PDocVariantData
|
|
read GetArrayOrAddByName;
|
|
/// direct access to a dvArray's TDocVariant property from its index
|
|
// - simple values may directly use Values[] dynamic array, but to access
|
|
// a TDocVariantData members, this property is safer
|
|
// - follows dvoReturnNullForUnknownProperty option to raise an exception
|
|
// - _[ndx] would return a fake void TDocVariant if aIndex is out of range,
|
|
// if the property is not existing or not a TDocVariantData (just like
|
|
// GetAsDocVariantSafe)
|
|
property _[aIndex: integer]: PDocVariantData
|
|
read GetAsDocVariantByIndex;
|
|
/// direct access to a dvObject value stored property value from its path name
|
|
// - default Value[] will check only names in the current object properties,
|
|
// whereas this property will recognize e.g. 'parent.child' nested objects
|
|
// - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
|
|
property P[const aNameOrPath: RawUtf8]: Variant
|
|
read GetVariantByPath;
|
|
end;
|
|
{$A+} { packet object not allowed since Delphi 2009 :( }
|
|
|
|
var
|
|
/// the internal custom variant type used to register TDocVariant
|
|
DocVariantType: TDocVariant;
|
|
|
|
/// copy of DocVariantType.VarType
|
|
// - as used by inlined functions of TDocVariantData
|
|
DocVariantVType: cardinal;
|
|
|
|
// defined here for inlining - properly filled in initialization section below
|
|
DV_FAST: array[TDocVariantKind] of TVarData;
|
|
|
|
|
|
/// retrieve the text representation of a TDocVairnatKind
|
|
function ToText(kind: TDocVariantKind): PShortString; overload;
|
|
|
|
/// direct access to a TDocVariantData from a given variant instance
|
|
// - return a pointer to the TDocVariantData corresponding to the variant
|
|
// instance, which may be of kind varByRef (e.g. when retrieved by late binding)
|
|
// - raise an EDocVariant exception if the instance is not a TDocVariant
|
|
// - the following direct trans-typing may fail, e.g. for varByRef value:
|
|
// ! TDocVariantData(aVarDoc.ArrayProp).Add('new item');
|
|
// - so you can write the following:
|
|
// ! DocVariantData(aVarDoc.ArrayProp).AddItem('new item');
|
|
// - note: due to a local variable lifetime change in Delphi 11, don't use
|
|
// this function with a temporary variant (e.g. from TList<variant>.GetItem) -
|
|
// call _DV() and a local TDocVariantData instead of a PDocVariantData
|
|
function DocVariantData(const DocVariant: variant): PDocVariantData;
|
|
|
|
const
|
|
/// constant used e.g. by _Safe() and _DV() overloaded functions
|
|
// - will be in code section of the exe, so will be read-only by design
|
|
// - would have Kind=dvUndefined and Count=0, so _Safe() would return
|
|
// a valid, but void document
|
|
// - its VType is varNull, so would be viewed as a null variant
|
|
// - dvoReturnNullForUnknownProperty is defined, so that U[]/I[]... methods
|
|
// won't raise any exception about unexpected field name
|
|
DocVariantDataFake: TDocVariantData = (
|
|
VType: varNull;
|
|
VOptions: [dvoReturnNullForUnknownProperty]{%H-});
|
|
|
|
/// direct access to a TDocVariantData from a given variant instance
|
|
// - return a pointer to the TDocVariantData corresponding to the variant
|
|
// instance, which may be of kind varByRef (e.g. when retrieved by late binding)
|
|
// - will return a read-only fake TDocVariantData with Kind=dvUndefined if the
|
|
// supplied variant is not a TDocVariant instance, so could be safely used
|
|
// in a with block (use "with" moderation, of course):
|
|
// ! with _Safe(aDocVariant)^ do
|
|
// ! for ndx := 0 to Count-1 do // here Count=0 for the "fake" result
|
|
// ! writeln(Names[ndx]);
|
|
// or excluding the "with" statement, as more readable code:
|
|
// ! var dv: PDocVariantData;
|
|
// ! ndx: PtrInt;
|
|
// ! begin
|
|
// ! dv := _Safe(aDocVariant);
|
|
// ! for ndx := 0 to dv.Count-1 do // here Count=0 for the "fake" result
|
|
// ! writeln(dv.Names[ndx]);
|
|
// - note: due to a local variable lifetime change in Delphi 11, don't use
|
|
// this function with a temporary variant (e.g. from TList<variant>.GetItem) -
|
|
// call _DV() and a local TDocVariantData instead of a PDocVariantData
|
|
function _Safe(const DocVariant: variant): PDocVariantData; overload;
|
|
{$ifdef FPC}inline;{$endif} // Delphi has problems inlining this :(
|
|
|
|
/// direct access to a TDocVariantData from a given variant instance
|
|
// - return a pointer to the TDocVariantData corresponding to the variant
|
|
// instance, which may be of kind varByRef (e.g. when retrieved by late binding)
|
|
// - will check the supplied document kind, i.e. either dvObject or dvArray and
|
|
// raise a EDocVariant exception if it does not match
|
|
// - note: due to a local variable lifetime change in Delphi 11, don't use
|
|
// this function with a temporary variant (e.g. from TList<variant>.GetItem) -
|
|
// call _DV() and a local TDocVariantData instead of a PDocVariantData
|
|
function _Safe(const DocVariant: variant;
|
|
ExpectedKind: TDocVariantKind): PDocVariantData; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// direct access to a TDocVariantData from a given variant instance
|
|
// - return true and set DocVariant with a pointer to the TDocVariantData
|
|
// corresponding to the variant instance, which may be of kind varByRef
|
|
// (e.g. when retrieved by late binding)
|
|
// - return false if the supplied Value is not a TDocVariant, but e.g. a string,
|
|
// a number or another type of custom variant
|
|
// - note: due to a local variable lifetime change in Delphi 11, don't use
|
|
// this function with a temporary variant (e.g. from TList<variant>.GetItem) -
|
|
// call _DV() and a local TDocVariantData instead of a PDocVariantData
|
|
function _Safe(const DocVariant: variant; out DV: PDocVariantData): boolean; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// direct access to a TDocVariantData array from a given variant instance
|
|
// - return true and set DV with a pointer to the TDocVariantData
|
|
// corresponding to the variant instance, if it is a dvArray
|
|
// - return false if the supplied Value is not an array TDocVariant
|
|
// - note: due to a local variable lifetime change in Delphi 11, don't use
|
|
// this function with a temporary variant (e.g. from TList<variant>.GetItem) -
|
|
// call _DV() and a local TDocVariantData instead of a PDocVariantData
|
|
function _SafeArray(const Value: variant; out DV: PDocVariantData): boolean; overload;
|
|
|
|
/// direct access to a TDocVariantData array from a given variant instance
|
|
// - overload to check for a given number of itemsin the array
|
|
function _SafeArray(const Value: variant; ExpectedCount: integer;
|
|
out DV: PDocVariantData): boolean; overload;
|
|
|
|
/// direct access to a TDocVariantData object from a given variant instance
|
|
// - return true and set DV with a pointer to the TDocVariantData
|
|
// corresponding to the variant instance, if it is a dvObject
|
|
// - return false if the supplied Value is not an object TDocVariant
|
|
// - note: due to a local variable lifetime change in Delphi 11, don't use
|
|
// this function with a temporary variant (e.g. from TList<variant>.GetItem) -
|
|
// call _DV() and a local TDocVariantData instead of a PDocVariantData
|
|
function _SafeObject(const Value: variant; out DV: PDocVariantData): boolean;
|
|
|
|
/// direct copy of a TDocVariantData from a given variant instance
|
|
// - slower, but maybe used instead of _Safe() e.g. on Delphi 11
|
|
function _DV(const DocVariant: variant): TDocVariantData; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// direct copy of a TDocVariantData from a given variant instance
|
|
// - slower, but maybe used instead of _Safe() e.g. on Delphi 11
|
|
function _DV(const DocVariant: variant;
|
|
ExpectedKind: TDocVariantKind): TDocVariantData; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// direct copy of a TDocVariantData from a given variant instance
|
|
// - slower, but maybe used instead of _Safe() e.g. on Delphi 11
|
|
function _DV(const DocVariant: variant;
|
|
var DV: TDocVariantData): boolean; overload;
|
|
{$ifdef FPC}inline;{$endif} // Delphi has troubles inlining goto/label
|
|
|
|
/// initialize a variant instance to store some document-based object content
|
|
// - object will be initialized with data supplied two by two, as Name,Value
|
|
// pairs, e.g.
|
|
// ! aVariant := _Obj(['name','John','year',1972]);
|
|
// or even with nested objects:
|
|
// ! aVariant := _Obj(['name','John','doc',_Obj(['one',1,'two',2.0])]);
|
|
// - this global function is an alias to TDocVariant.NewObject()
|
|
// - by default, every internal value will be copied, so access of nested
|
|
// properties can be slow - if you expect the data to be read-only or not
|
|
// propagated into another place, set Options=[dvoValueCopiedByReference]
|
|
// or using _ObjFast() will increase the process speed a lot
|
|
function _Obj(const NameValuePairs: array of const;
|
|
Options: TDocVariantOptions = []): variant;
|
|
|
|
/// add a property value to a document-based object content
|
|
// - if Obj is a TDocVariant object, will add the Name/Value pair
|
|
// - if Obj is not a TDocVariant, will create a new fast document,
|
|
// initialized with supplied the Name/Value pairs
|
|
// - this function will also ensure that ensure Obj is not stored by reference,
|
|
// but as a true TDocVariantData
|
|
procedure _ObjAddProp(const Name: RawUtf8; const Value: variant;
|
|
var Obj: variant); overload;
|
|
|
|
/// add a document property value to a document-based object content
|
|
procedure _ObjAddProp(const Name: RawUtf8; const Value: TDocVariantData;
|
|
var Obj: variant); overload;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
|
|
/// add a RawUtf8 property value to a document-based object content
|
|
procedure _ObjAddPropU(const Name: RawUtf8; const Value: RawUtf8;
|
|
var Obj: variant);
|
|
|
|
/// add some property values to a document-based object content
|
|
// - if Obj is a TDocVariant object, will add the Name/Value pairs
|
|
// - if Obj is not a TDocVariant, will create a new fast document,
|
|
// initialized with supplied the Name/Value pairs
|
|
// - this function will also ensure that ensure Obj is not stored by reference,
|
|
// but as a true TDocVariantData
|
|
procedure _ObjAddProps(const NameValuePairs: array of const;
|
|
var Obj: variant); overload;
|
|
|
|
/// add the property values of a document to a document-based object content
|
|
// - if Document is not a TDocVariant object, will do nothing
|
|
// - if Obj is a TDocVariant object, will add Document fields to its content
|
|
// - if Obj is not a TDocVariant object, Document will be copied to Obj
|
|
procedure _ObjAddProps(const Document: variant;
|
|
var Obj: variant); overload;
|
|
|
|
/// initialize a variant instance to store some document-based array content
|
|
// - array will be initialized with data supplied as parameters, e.g.
|
|
// ! aVariant := _Arr(['one',2,3.0]);
|
|
// - this global function is an alias to TDocVariant.NewArray()
|
|
// - by default, every internal value will be copied, so access of nested
|
|
// properties can be slow - if you expect the data to be read-only or not
|
|
// propagated into another place, set Options = [dvoValueCopiedByReference]
|
|
// or using _ArrFast() will increase the process speed a lot
|
|
function _Arr(const Items: array of const;
|
|
Options: TDocVariantOptions = []): variant;
|
|
|
|
/// initialize a variant instance to store some document-based content
|
|
// from a supplied (extended) JSON content
|
|
// - this global function is an alias to TDocVariant.NewJson(), and return
|
|
// an Unassigned (varEmpty) variant if JSON content was not correctly converted
|
|
// - object or array will be initialized from the supplied JSON content, e.g.
|
|
// ! aVariant := _Json('{"id":10,"doc":{"name":"John","birthyear":1972}}');
|
|
// ! // now you can access to the properties via late binding
|
|
// ! assert(aVariant.id=10);
|
|
// ! assert(aVariant.doc.name='John');
|
|
// ! assert(aVariant.doc.birthYear=1972);
|
|
// ! // and also some pseudo-properties:
|
|
// ! assert(aVariant._count=2);
|
|
// ! assert(aVariant.doc._kind=ord(dvObject));
|
|
// ! // or with a JSON array:
|
|
// ! aVariant := _Json('["one",2,3]');
|
|
// ! assert(aVariant._kind=ord(dvArray));
|
|
// ! for i := 0 to aVariant._count-1 do
|
|
// ! writeln(aVariant._(i));
|
|
// - in addition to the JSON RFC specification strict mode, this method will
|
|
// handle some BSON-like extensions, e.g. unquoted field names:
|
|
// ! aVariant := _Json('{id:10,doc:{name:"John",birthyear:1972}}');
|
|
// - if the mormot.db.nosql.bson unit is used in the application, the MongoDB
|
|
// Shell syntax will also be recognized to create TBsonVariant, like
|
|
// ! new Date() ObjectId() MinKey MaxKey /<jRegex>/<jOptions>
|
|
// see @http://docs.mongodb.org/manual/reference/mongodb-extended-json
|
|
// - by default, every internal value will be copied, so access of nested
|
|
// properties can be slow - if you expect the data to be read-only or not
|
|
// propagated into another place, add dvoValueCopiedByReference in Options
|
|
// will increase the process speed a lot, or use _JsonFast()
|
|
// - handle only currency for floating point values: call _JsonFastFloat or set
|
|
// dvoAllowDoubleValue option to support double, with potential precision loss
|
|
function _Json(const Json: RawUtf8;
|
|
Options: TDocVariantOptions = [dvoReturnNullForUnknownProperty]): variant; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// initialize a variant instance to store some document-based content
|
|
// from a supplied (extended) JSON content, with parameters formating
|
|
// - wrapper around the _Json(FormatUtf8(...,JsonFormat=true)) function,
|
|
// i.e. every Args[] will be inserted for each % and Params[] for each ?,
|
|
// with proper JSON escaping of string values, and writing nested _Obj() /
|
|
// _Arr() instances as expected JSON objects / arrays
|
|
// - typical use (in the context of mormot.db.nosql.bson unit) could be:
|
|
// ! aVariant := _JsonFmt('{%:{$in:[?,?]}}',['type'],['food','snack']);
|
|
// ! aVariant := _JsonFmt('{type:{$in:?}}',[],[_Arr(['food','snack'])]);
|
|
// ! // which are the same as:
|
|
// ! aVariant := _JsonFmt('{type:{$in:["food","snack"]}}');
|
|
// ! // in this context:
|
|
// ! u := VariantSaveJson(aVariant);
|
|
// ! assert(u='{"type":{"$in":["food","snack"]}}');
|
|
// ! u := VariantSaveMongoJson(aVariant,modMongoShell);
|
|
// ! assert(u='{type:{$in:["food","snack"]}}');
|
|
// - by default, every internal value will be copied, so access of nested
|
|
// properties can be slow - if you expect the data to be read-only or not
|
|
// propagated into another place, add dvoValueCopiedByReference in Options
|
|
// will increase the process speed a lot, or use _JsonFast()
|
|
function _JsonFmt(const Format: RawUtf8; const Args, Params: array of const;
|
|
Options: TDocVariantOptions = [dvoReturnNullForUnknownProperty]): variant; overload;
|
|
|
|
/// initialize a variant instance to store some document-based content
|
|
// from a supplied (extended) JSON content, with parameters formating
|
|
// - this overload function will set directly a local variant variable,
|
|
// and would be used by inlined _JsonFmt/_JsonFastFmt functions
|
|
procedure _JsonFmt(const Format: RawUtf8; const Args, Params: array of const;
|
|
Options: TDocVariantOptions; out Result: variant); overload;
|
|
|
|
/// initialize a variant instance to store some document-based content
|
|
// from a supplied (extended) JSON content
|
|
// - this global function is an alias to TDocVariant.NewJson(), and
|
|
// will return TRUE if JSON content was correctly converted into a variant
|
|
// - in addition to the JSON RFC specification strict mode, this method will
|
|
// handle some BSON-like extensions, e.g. unquoted field names or ObjectID()
|
|
// - by default, every internal value will be copied, so access of nested
|
|
// properties can be slow - if you expect the data to be read-only or not
|
|
// propagated into another place, add dvoValueCopiedByReference in Options
|
|
// will increase the process speed a lot, or use _JsonFast()
|
|
function _Json(const Json: RawUtf8; var Value: variant;
|
|
Options: TDocVariantOptions = [dvoReturnNullForUnknownProperty]): boolean; overload;
|
|
|
|
/// initialize a variant instance to store some document-based object content
|
|
// - this global function is an handy alias to:
|
|
// ! Obj(NameValuePairs, JSON_FAST);
|
|
// - so all created objects and arrays will be handled by reference, for best
|
|
// speed - but you should better write on the resulting variant tree with caution
|
|
function _ObjFast(const NameValuePairs: array of const): variant; overload;
|
|
|
|
/// initialize a variant instance to store any object as a TDocVariant
|
|
// - is a wrapper around ObjectToVariant(aObject, result, aOptions)
|
|
function _ObjFast(aObject: TObject;
|
|
aOptions: TTextWriterWriteObjectOptions = [woDontStoreDefault]): variant; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// initialize a variant instance to store some document-based array content
|
|
// - this global function is an handy alias to:
|
|
// ! _Array(Items, JSON_FAST);
|
|
// - so all created objects and arrays will be handled by reference, for best
|
|
// speed - but you should better write on the resulting variant tree with caution
|
|
function _ArrFast(const Items: array of const): variant; overload;
|
|
|
|
/// initialize a variant instance to store some document-based content
|
|
// from a supplied (extended) JSON content
|
|
// - this global function is an handy alias to:
|
|
// ! _Json(JSON, JSON_FAST);
|
|
// so returns an Unassigned (varEmpty) variant if JSON content was not correct
|
|
// - so all created objects and arrays will be handled by reference, for best
|
|
// speed - but you should better write on the resulting variant tree with caution
|
|
// - in addition to the JSON RFC specification strict mode, this method will
|
|
// handle some BSON-like extensions, e.g. unquoted field names or ObjectID()
|
|
// - will handle only currency for floating point values to avoid precision
|
|
// loss: use _JsonFastFloat() instead if you want to support double values
|
|
function _JsonFast(const Json: RawUtf8): variant;
|
|
|
|
/// initialize a variant instance to store some document-based content
|
|
// from a supplied (extended) JSON content, with double conversion
|
|
// - _JsonFast() will support only currency floats: use this method instead
|
|
// if your JSON input is likely to require double values - with potential
|
|
// precision loss
|
|
function _JsonFastFloat(const Json: RawUtf8): variant;
|
|
|
|
/// initialize a variant instance to store some extended document-based content
|
|
// - this global function is an handy alias to:
|
|
// ! _Json(JSON, JSON_FAST_EXTENDED);
|
|
function _JsonFastExt(const Json: RawUtf8): variant;
|
|
|
|
/// initialize a variant instance to store some document-based content
|
|
// from a supplied (extended) JSON content, with parameters formating
|
|
// - this global function is an handy alias e.g. to:
|
|
// ! aVariant := _JsonFmt('{%:{$in:[?,?]}}',['type'],['food','snack'], JSON_FAST);
|
|
// - so all created objects and arrays will be handled by reference, for best
|
|
// speed - but you should better write on the resulting variant tree with caution
|
|
// - in addition to the JSON RFC specification strict mode, this method will
|
|
// handle some BSON-like extensions, e.g. unquoted field names or ObjectID():
|
|
function _JsonFastFmt(const Format: RawUtf8;
|
|
const Args, Params: array of const): variant;
|
|
|
|
/// ensure a document-based variant instance will have only per-value nested
|
|
// objects or array documents
|
|
// - is just a wrapper around:
|
|
// ! TDocVariantData(DocVariant).InitCopy(DocVariant, JSON_[mDefault])
|
|
// - you can use this function to ensure that all internal properties of this
|
|
// variant will be copied per-value whatever options the nested objects or
|
|
// arrays were created with
|
|
// - for huge document with a big depth of nested objects or arrays, a full
|
|
// per-value copy may be time and resource consuming, but will be also safe
|
|
// - will raise an EDocVariant if the supplied variant is not a TDocVariant or
|
|
// a varByRef pointing to a TDocVariant
|
|
procedure _Unique(var DocVariant: variant);
|
|
|
|
/// ensure a document-based variant instance will have only per-value nested
|
|
// objects or array documents
|
|
// - is just a wrapper around:
|
|
// ! TDocVariantData(DocVariant).InitCopy(DocVariant, JSON_FAST)
|
|
// - you can use this function to ensure that all internal properties of this
|
|
// variant will be copied per-reference whatever options the nested objects or
|
|
// arrays were created with
|
|
// - for huge document with a big depth of nested objects or arrays, it will
|
|
// first create a whole copy of the document nodes, but further assignments
|
|
// of the resulting value will be per-reference, so will be almost instant
|
|
// - will raise an EDocVariant if the supplied variant is not a TDocVariant or
|
|
// a varByRef pointing to a TDocVariant
|
|
procedure _UniqueFast(var DocVariant: variant);
|
|
|
|
/// return a full nested copy of a document-based variant instance
|
|
// - is just a wrapper around:
|
|
// ! TDocVariant.NewUnique(DocVariant,JSON_[mDefault])
|
|
// - you can use this function to ensure that all internal properties of this
|
|
// variant will be copied per-value whatever options the nested objects or
|
|
// arrays were created with: to be used on a value returned as varByRef
|
|
// (e.g. by _() pseudo-method)
|
|
// - for huge document with a big depth of nested objects or arrays, a full
|
|
// per-value copy may be time and resource consuming, but will be also safe -
|
|
// consider using _ByRef() instead if a fast copy-by-reference is enough
|
|
// - will raise an EDocVariant if the supplied variant is not a TDocVariant or
|
|
// a varByRef pointing to a TDocVariant
|
|
function _Copy(const DocVariant: variant): variant;
|
|
|
|
/// return a full nested copy of a document-based variant instance
|
|
// - is just a wrapper around:
|
|
// ! TDocVariant.NewUnique(DocVariant, JSON_FAST)
|
|
// - you can use this function to ensure that all internal properties of this
|
|
// variant will be copied per-value whatever options the nested objects or
|
|
// arrays were created with: to be used on a value returned as varByRef
|
|
// (e.g. by _() pseudo-method)
|
|
// - for huge document with a big depth of nested objects or arrays, a full
|
|
// per-value copy may be time and resource consuming, but will be also safe -
|
|
// consider using _ByRef() instead if a fast copy-by-reference is enough
|
|
// - will raise an EDocVariant if the supplied variant is not a TDocVariant or
|
|
// a varByRef pointing to a TDocVariant
|
|
function _CopyFast(const DocVariant: variant): variant;
|
|
|
|
/// copy a TDocVariant to another variable, changing the options on the fly
|
|
// - note that the content (items or properties) is copied by reference,
|
|
// so consider using _Copy() instead if you expect to safely modify its content
|
|
// - will return null if the supplied variant is not a TDocVariant
|
|
function _ByRef(const DocVariant: variant;
|
|
Options: TDocVariantOptions): variant; overload;
|
|
|
|
/// copy a TDocVariant to another variable, changing the options on the fly
|
|
// - note that the content (items or properties) is copied by reference,
|
|
// so consider using _Copy() instead if you expect to safely modify its content
|
|
// - will return null if the supplied variant is not a TDocVariant
|
|
procedure _ByRef(const DocVariant: variant; out Dest: variant;
|
|
Options: TDocVariantOptions); overload;
|
|
|
|
/// convert a TDocVariantData array or a string value into a CSV
|
|
// - will call either TDocVariantData.ToCsv, or return the string
|
|
// - returns '' if the supplied value is neither a TDocVariant or a string
|
|
// - could be used e.g. to store either a JSON CSV string or a JSON array of
|
|
// strings in a settings property
|
|
function _Csv(const DocVariantOrString: variant): RawUtf8;
|
|
|
|
/// will convert any TObject into a TDocVariant document instance
|
|
// - fast processing function as used by _ObjFast(Value)
|
|
// - note that the result variable should already be cleared: no VarClear()
|
|
// is done by this function
|
|
// - would be used e.g. by VarRecToVariant() function
|
|
// - if you expect lazy-loading of a TObject, see TObjectVariant.New()
|
|
procedure ObjectToVariant(Value: TObject; var result: variant;
|
|
Options: TTextWriterWriteObjectOptions = [woDontStoreDefault]); overload;
|
|
|
|
/// will convert any TObject into a TDocVariant document instance
|
|
// - convenient overloaded function to include woEnumSetsAsText option
|
|
function ObjectToVariant(Value: TObject; EnumSetsAsText: boolean): variant; overload;
|
|
|
|
/// will serialize any TObject into a TDocVariant debugging document
|
|
// - just a wrapper around _JsonFast(ObjectToJsonDebug()) with an optional
|
|
// "Context":"..." text message
|
|
// - if the supplied context format matches '{....}' then it will be added
|
|
// as a corresponding TDocVariant JSON object
|
|
function ObjectToVariantDebug(Value: TObject;
|
|
const ContextFormat: RawUtf8; const ContextArgs: array of const;
|
|
const ContextName: RawUtf8 = 'context'): variant; overload;
|
|
|
|
/// get the enumeration names corresponding to a set value, as a JSON array
|
|
function SetNameToVariant(Value: cardinal; Info: TRttiCustom;
|
|
FullSetsAsStar: boolean = false): variant; overload;
|
|
|
|
/// get the enumeration names corresponding to a set value, as a JSON array
|
|
function SetNameToVariant(Value: cardinal; Info: PRttiInfo;
|
|
FullSetsAsStar: boolean = false): variant; overload;
|
|
|
|
/// fill a class instance from a TDocVariant object document properties
|
|
// - returns FALSE if the variant is not a dvObject, TRUE otherwise
|
|
function DocVariantToObject(var doc: TDocVariantData; obj: TObject;
|
|
objRtti: TRttiCustom = nil): boolean;
|
|
|
|
/// fill a T*ObjArray variable from a TDocVariant array document values
|
|
// - will always erase the T*ObjArray instance, and fill it from arr values
|
|
procedure DocVariantToObjArray(var arr: TDocVariantData; var objArray;
|
|
objClass: TClass);
|
|
|
|
/// will convert a blank TObject into a TDocVariant document instance
|
|
function ObjectDefaultToVariant(aClass: TClass;
|
|
aOptions: TDocVariantOptions): variant; overload;
|
|
|
|
|
|
|
|
{ ************** JSON Parsing into Variant }
|
|
|
|
/// low-level function to set a variant from an unescaped JSON number or string
|
|
// - expect the JSON input buffer to be already unescaped and #0 terminated,
|
|
// e.g. by TGetJsonField, and having set properly the wasString flag
|
|
// - set the varString or call GetVariantFromNotStringJson() if TryCustomVariants=nil
|
|
// - or call JsonToAnyVariant() to support TryCustomVariants^ complex input
|
|
procedure GetVariantFromJsonField(Json: PUtf8Char; wasString: boolean;
|
|
var Value: variant; TryCustomVariants: PDocVariantOptions = nil;
|
|
AllowDouble: boolean = false; JsonLen: integer = 0);
|
|
|
|
/// low-level function to set a variant from an unescaped JSON non string
|
|
// - expect the JSON input buffer to be already unescaped and #0 terminated,
|
|
// e.g. by TGetJsonField, and having returned wasString=false
|
|
// - is called e.g. by function GetVariantFromJsonField()
|
|
// - will recognize null, boolean, integer, Int64, currency, double
|
|
// (if AllowDouble is true) input, then set Value and return TRUE
|
|
// - returns FALSE if the supplied input has no expected JSON format
|
|
function GetVariantFromNotStringJson(Json: PUtf8Char;
|
|
var Value: TVarData; AllowDouble: boolean): boolean;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// low-level function to parse a JSON buffer content into a variant
|
|
// - warning: will decode in the Json buffer memory itself (no memory
|
|
// allocation or copy), for faster process - so take care that it is not shared
|
|
// - internal method used by VariantLoadJson(), GetVariantFromJsonField() and
|
|
// TDocVariantData.InitJson()
|
|
// - will instantiate either an integer, Int64, currency, double or string value
|
|
// (as RawUtf8), guessing the best numeric type according to the textual content,
|
|
// and string in all other cases, except TryCustomVariants points to some options
|
|
// (e.g. @JSON_[mFast] for fast instance) and input is a known object or
|
|
// array, either encoded as strict-JSON (i.e. {..} or [..]), or with some
|
|
// extended (e.g. BSON) syntax
|
|
procedure JsonToAnyVariant(var Value: variant; var Info: TGetJsonField;
|
|
Options: PDocVariantOptions; AllowDouble: boolean = false);
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
/// low-level function to parse a JSON content into a variant
|
|
procedure GetJsonToAnyVariant(var Value: variant; var Json: PUtf8Char;
|
|
EndOfObject: PUtf8Char; Options: PDocVariantOptions; AllowDouble: boolean);
|
|
overload; {$ifdef HASINLINE}inline;{$endif}
|
|
{$endif PUREMORMOT2}
|
|
|
|
/// identify either varInt64, varDouble, varCurrency types following JSON format
|
|
// - any non valid number is returned as varString
|
|
// - warning: supplied JSON is expected to be not nil
|
|
function TextToVariantNumberType(Json: PUtf8Char): cardinal;
|
|
|
|
/// identify either varInt64 or varCurrency types following JSON format
|
|
// - this version won't return varDouble, i.e. won't handle more than 4 exact
|
|
// decimals (as varCurrency), nor scientific notation with exponent (1.314e10)
|
|
// - this will ensure that any incoming JSON will converted back with its exact
|
|
// textual representation, without digit truncation due to limited precision
|
|
// - any non valid number is returned as varString
|
|
// - warning: supplied JSON is expected to be not nil
|
|
function TextToVariantNumberTypeNoDouble(Json: PUtf8Char): cardinal;
|
|
|
|
/// low-level function to parse a variant from an unescaped JSON number
|
|
// - returns the position after the number, and set Value to a variant of type
|
|
// varInteger/varInt64/varCurrency (or varDouble if AllowVarDouble is true)
|
|
// - returns nil if JSON can't be converted to a number - it is likely a string
|
|
// - handle only up to 4 decimals (i.e. currency) if AllowVarDouble is false
|
|
// - matches TextToVariantNumberType/TextToVariantNumberTypeNoDouble() logic
|
|
// - see GetVariantFromNotStringJson() to check the whole Json input, and
|
|
// parse null/false/true values
|
|
function GetNumericVariantFromJson(Json: PUtf8Char;
|
|
var Value: TVarData; AllowVarDouble: boolean): PUtf8Char;
|
|
|
|
/// convert some UTF-8 into a variant, detecting JSON numbers or constants
|
|
// - first try GetVariantFromNotStringJson() then fallback to RawUtf8ToVariant()
|
|
procedure TextToVariant(const aValue: RawUtf8; AllowVarDouble: boolean;
|
|
out aDest: variant);
|
|
|
|
/// convert some UTF-8 buffer into a variant, detecting JSON numbers or constants
|
|
// - first try GetVariantFromNotStringJson() then fallback to RawUtf8ToVariant()
|
|
procedure TextBufferToVariant(aValue: PUtf8Char; AllowVarDouble: boolean;
|
|
out aDest: variant);
|
|
|
|
/// convert some UTF-8 text buffer into a variant, with string interning
|
|
// - similar to TextToVariant(), but with string interning (if Interning<>nil)
|
|
// - first try GetVariantFromNotStringJson() then fallback to RawUtf8ToVariant()
|
|
procedure UniqueVariant(Interning: TRawUtf8Interning;
|
|
var aResult: variant; aText: PUtf8Char; aTextLen: PtrInt;
|
|
aAllowVarDouble: boolean = false); overload;
|
|
|
|
/// convert the next CSV item into a variant number or RawUtf8 varString
|
|
// - just a wrapper around GetNextItem() + TextToVariant()
|
|
function GetNextItemToVariant(var P: PUtf8Char;
|
|
out Value: Variant; Sep: AnsiChar = ','; AllowDouble: boolean = true): boolean;
|
|
|
|
/// retrieve a variant value from a JSON number or string
|
|
// - follows TJsonWriter.AddVariant() format (calls JsonToAnyVariant)
|
|
// - make a temporary copy before parsing - use JsonToAnyVariant() on a buffer
|
|
// - return true and set Value on success, or false and empty Value on error
|
|
function VariantLoadJson(var Value: Variant; const Json: RawUtf8;
|
|
TryCustomVariants: PDocVariantOptions = nil;
|
|
AllowDouble: boolean = false): boolean; overload;
|
|
|
|
/// retrieve a variant value from a JSON number or string
|
|
// - just wrap VariantLoadJson(Value,Json...) procedure as a function
|
|
function VariantLoadJson(const Json: RawUtf8;
|
|
TryCustomVariants: PDocVariantOptions = nil;
|
|
AllowDouble: boolean = false): variant; overload;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
|
|
/// just a wrapper around VariantLoadJson() with some TDocVariantOptions
|
|
// - make a temporary copy of the input Json before parsing
|
|
function JsonToVariant(const Json: RawUtf8;
|
|
Options: TDocVariantOptions = [dvoReturnNullForUnknownProperty];
|
|
AllowDouble: boolean = false): variant;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
|
|
/// just a wrapper around JsonToAnyVariant() with some TDocVariantOptions
|
|
function JsonToVariantInPlace(var Value: Variant; Json: PUtf8Char;
|
|
Options: TDocVariantOptions = [dvoReturnNullForUnknownProperty];
|
|
AllowDouble: boolean = false): PUtf8Char;
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
|
|
/// decode multipart/form-data POST request content into a TDocVariantData
|
|
// - following RFC 1867
|
|
// - decoded sections are encoded as Doc JSON object with its textual values,
|
|
// or with nested objects, if the data was supplied as binary:
|
|
// ! {"name1":{"data":..,"filename":...,"contenttype":...},"name2":...}
|
|
procedure MultiPartToDocVariant(const MultiPart: TMultiPartDynArray;
|
|
var Doc: TDocVariantData; Options: PDocVariantOptions = nil);
|
|
|
|
/// parse a "key<value" or "key<" expression for SortMatch() comparison
|
|
function ParseSortMatch(Expression: PUtf8Char; out Key: RawUtf8;
|
|
out Match: TCompareOperator; Value: PVariant): boolean;
|
|
|
|
|
|
{ ************** Variant Binary Serialization }
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
|
|
/// compute the number of bytes needed to save a Variant content
|
|
// using the VariantSave() function
|
|
// - will return 0 in case of an invalid (not handled) Variant type
|
|
// - deprecated function - use overloaded BinarySave() functions instead
|
|
function VariantSaveLength(const Value: variant): integer; deprecated;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
|
|
/// save a Variant content into a destination memory buffer
|
|
// - Dest must be at least VariantSaveLength() bytes long
|
|
// - will handle standard Variant types and custom types (serialized as JSON)
|
|
// - will return nil in case of an invalid (not handled) Variant type
|
|
// - will use a proprietary binary format, with some variable-length encoding
|
|
// of the string length
|
|
// - warning: will encode RTL string fields as within the variant type
|
|
// itself: using this function between UNICODE and NOT UNICODE
|
|
// versions of Delphi, will propably fail - you have been warned!
|
|
// - deprecated function - use overloaded BinarySave() functions instead
|
|
function VariantSave(const Value: variant; Dest: PAnsiChar): PAnsiChar;
|
|
overload; deprecated; {$ifdef HASINLINE}inline;{$endif}
|
|
|
|
{$endif PUREMORMOT2}
|
|
|
|
/// save a Variant content into a binary buffer
|
|
// - will handle standard Variant types and custom types (serialized as JSON)
|
|
// - will return '' in case of an invalid (not handled) Variant type
|
|
// - just a wrapper around VariantSaveLength()+VariantSave()
|
|
// - warning: will encode RTL string fields as within the variant type
|
|
// itself: using this function between UNICODE and NOT UNICODE
|
|
// versions of Delphi, will propably fail - you have been warned!
|
|
// - is a wrapper around BinarySave(rkVariant)
|
|
function VariantSave(const Value: variant): RawByteString; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// retrieve a variant value from our optimized binary serialization format
|
|
// - follow the data layout as used by RecordLoad() or VariantSave() function
|
|
// - return nil if the Source buffer is incorrect
|
|
// - in case of success, return the memory buffer pointer just after the
|
|
// read content
|
|
// - how custom type variants are created can be defined via CustomVariantOptions
|
|
// - is a wrapper around BinaryLoad(rkVariant)
|
|
function VariantLoad(var Value: variant; Source: PAnsiChar;
|
|
CustomVariantOptions: PDocVariantOptions;
|
|
SourceMax: PAnsiChar {$ifndef PUREMORMOT2} = nil {$endif}): PAnsiChar; overload;
|
|
|
|
/// retrieve a variant value from our optimized binary serialization format
|
|
// - follow the data layout as used by RecordLoad() or VariantSave() function
|
|
// - return varEmpty if the Source buffer is incorrect
|
|
// - just a wrapper around VariantLoad()
|
|
// - how custom type variants are created can be defined via CustomVariantOptions
|
|
// - is a wrapper around BinaryLoad(rkVariant)
|
|
function VariantLoad(const Bin: RawByteString;
|
|
CustomVariantOptions: PDocVariantOptions): variant; overload;
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
/// retrieve a variant value from variable-length buffer
|
|
// - matches TFileBufferWriter.Write()
|
|
// - how custom type variants are created can be defined via CustomVariantOptions
|
|
// - is just a wrapper around VariantLoad/BinaryLoad
|
|
procedure FromVarVariant(var Source: PByte; var Value: variant;
|
|
CustomVariantOptions: PDocVariantOptions; SourceMax: PByte);
|
|
{$ifdef HASINLINE}inline;{$endif}
|
|
|
|
|
|
{ ************** IDocList/IDocDict advanced Wrappers of TDocVariant Documents }
|
|
|
|
type
|
|
IDocList = interface;
|
|
IDocDict = interface;
|
|
|
|
{$ifdef HASIMPLICITOPERATOR}
|
|
|
|
/// internal map of a variant memory structure for IDocList/IDocDict wrappers
|
|
// - implicit class operators are used to ease its work with high-level result
|
|
// variables like RawUtf8 or IDocList/IDocDict - even as iterator variables
|
|
// - warning: is a weak reference to the main IDocList/IDocDict, so you need to
|
|
// explicitly convert it into stand-alone variables to use outside of the loop
|
|
TDocValue = record
|
|
public
|
|
/// raw pointer access to the corresponting value IDocList/IDocDict
|
|
// - you should not use this field, but implicit operators
|
|
V: PVariant;
|
|
/// returns true if the current value is a string
|
|
function IsString: boolean; inline;
|
|
/// returns the high-level TDocVariant type of the current value
|
|
// - i.e. dvArray for a IDocList, dvObject for a IDocDict and dvUndefined
|
|
// for any other value (e.g. string, number...)
|
|
function Kind: TDocVariantKind; { better not inlined }
|
|
public
|
|
class operator Implicit(const A: TDocValue): boolean; inline;
|
|
class operator Implicit(const A: TDocValue): integer; inline;
|
|
class operator Implicit(const A: TDocValue): Int64; inline;
|
|
class operator Implicit(const A: TDocValue): string; inline;
|
|
class operator Implicit(const A: TDocValue): RawUtf8; inline;
|
|
class operator Implicit(const A: TDocValue): IDocList; inline;
|
|
class operator Implicit(const A: TDocValue): IDocDict; inline;
|
|
class operator Implicit(const A: TDocValue): variant; inline;
|
|
class operator Implicit(const A: TDocValue): PVarData; inline;
|
|
class operator Implicit(const A: TDocValue): PDocVariantData;
|
|
end;
|
|
|
|
/// low-level Enumerator as returned by IDocList.GetEnumerator and
|
|
// IDocDict.Values
|
|
// - warning: weak reference to the main IDocList/IDocDict, so you need to
|
|
// explicitly convert it into stand-alone variables to use outside of the loop
|
|
TDocValueEnumerator = record
|
|
private
|
|
Curr, After: TDocValue;
|
|
public
|
|
function MoveNext: boolean; inline;
|
|
function GetEnumerator: TDocValueEnumerator; inline;
|
|
property Current: TDocValue
|
|
read Curr;
|
|
end;
|
|
|
|
/// low-level Enumerator as returned by IDocList.Objects
|
|
// - warning: weak reference to the main IDocList/IDocDict, so you need to
|
|
// explicitly call IDocDict.Copy to use outside of the loop
|
|
TDocObjectEnumerator = record
|
|
private
|
|
CurrDict: IDocDict; // a single instance reused during whole iteration
|
|
Curr, After: PVariant;
|
|
CurrDictValue: ^PDocVariantData;
|
|
CompKey: RawUtf8;
|
|
CompValue: Variant;
|
|
CompFunc: TVariantCompare;
|
|
CompMatch: TCompareOperator;
|
|
CompKeyHasPath: boolean;
|
|
CompKeyPrev: integer;
|
|
public
|
|
function MoveNext: boolean; { too complex to be inlined }
|
|
function GetEnumerator: TDocObjectEnumerator;
|
|
property Current: IDocDict
|
|
read CurrDict;
|
|
end;
|
|
|
|
/// internal map of a key name for IDocDict wrappers
|
|
// - warning: weak reference to the main IDocList/IDocDict, so you need to
|
|
// explicitly convert it into stand-alone variables to use outside of the loop
|
|
TDocKey = record
|
|
public
|
|
/// raw pointer access to the corresponting name
|
|
V: PRawUtf8;
|
|
function Equals(const txt: RawUtf8): boolean; inline;
|
|
function Utf8: RawUtf8; inline;
|
|
public
|
|
class operator Implicit(const A: TDocKey): string; inline;
|
|
class operator Implicit(const A: TDocKey): RawUtf8; inline;
|
|
end;
|
|
|
|
/// low-level Enumerator as returned by IDocDict.Keys
|
|
// - warning: weak reference to the main IDocList/IDocDict, so you need to
|
|
// explicitly convert it into stand-alone variables to use outside of the loop
|
|
TDocKeyEnumerator = record
|
|
private
|
|
Curr, After: TDocKey;
|
|
public
|
|
function MoveNext: boolean; inline;
|
|
function GetEnumerator: TDocKeyEnumerator; inline;
|
|
property Current: TDocKey
|
|
read Curr;
|
|
end;
|
|
|
|
/// store one key/value pair as returned by IDocDict.GetEnumerator
|
|
// - thanks to TDocKey/TDocValue wrappers, no transient copy is involved
|
|
// - warning: weak reference to the main IDocList/IDocDict, so you need to
|
|
// explicitly convert it into stand-alone variables to use outside of the loop
|
|
TDocDictFields = record
|
|
public
|
|
/// efficient/indirect access to one key of the current IDocDict element
|
|
Key: TDocKey;
|
|
/// efficient/indirect access to one value of the current IDocDict element
|
|
Value: TDocValue;
|
|
/// returns 'Key=Value' text
|
|
function KeyValue(const separator: RawUtf8 = '='): RawUtf8;
|
|
end;
|
|
|
|
/// low-level Enumerator as returned by IDocDict.GetEnumerator
|
|
// - warning: weak reference to the main IDocList/IDocDict, so you need to
|
|
// explicitly convert it into stand-alone variables to use outside of the loop
|
|
TDocDictEnumerator = record
|
|
private
|
|
Curr: TDocDictFields;
|
|
AfterValue: TDocValue;
|
|
public
|
|
function MoveNext: boolean; inline;
|
|
function GetEnumerator: TDocDictEnumerator; inline;
|
|
property Current: TDocDictFields
|
|
read Curr;
|
|
end;
|
|
|
|
{$endif HASIMPLICITOPERATOR}
|
|
|
|
/// abstract parent with common methods to IDocList/IDocDict wrappers
|
|
IDocAny = interface(ISerializable)
|
|
/// remove all elements from the list/dictionary
|
|
procedure Clear;
|
|
/// equals dvArray for IDocList, dvObject for IDocDict
|
|
function Kind: TDocVariantKind;
|
|
/// the known model of the internal TDocVariantData storage
|
|
function Model: TDocVariantModel;
|
|
/// how many items or name/value pairs are stored in this instance
|
|
function Len: integer;
|
|
/// returns itself as a IDocList, or nil if is a IDocDict
|
|
function AsList: IDocList;
|
|
/// returns itself as a IDocDic, or nil if is a IDocList
|
|
function AsDict: IDocDict;
|
|
/// returns the associated TDocVariant instance
|
|
function AsVariant: variant;
|
|
/// low-level access to the internal TDocVariantData storage
|
|
// - warning: is a weak reference pointer to the main IDocList/IDocDict, so
|
|
// you need to copy it to use it outside of this instance
|
|
function Value: PDocVariantData;
|
|
end;
|
|
|
|
/// exception raised by IDocList
|
|
EDocList = class(EDocVariant)
|
|
public
|
|
class procedure GetRaise(method: AnsiChar; pos: integer; const v: variant);
|
|
end;
|
|
|
|
/// exception raised by IDocDict
|
|
EDocDict = class(EDocVariant)
|
|
public
|
|
class procedure Error(method: AnsiChar; const key: RawUtf8; const v: variant);
|
|
end;
|
|
|
|
/// a dynamic array of IDocDict instances
|
|
IDocDictDynArray = array of IDocDict;
|
|
|
|
/// a List, used to store multiple values
|
|
// - implemented via an internal TDocVariantData dvArray
|
|
// - follows most Python Lists naming and conventions
|
|
IDocList = interface(IDocAny)
|
|
['{8186E3D3-3C9C-46E6-89D1-82ADDB741AAB}']
|
|
// methods used as getter/setter for properties
|
|
function GetB(position: integer): boolean;
|
|
function GetC(position: integer): currency;
|
|
function GetD(position: integer): IDocDict;
|
|
function GetF(position: integer): double;
|
|
function GetI(position: integer): Int64;
|
|
function GetItem(position: integer): variant;
|
|
function GetL(position: integer): IDocList;
|
|
function GetS(position: integer): string;
|
|
function GetU(position: integer): RawUtf8;
|
|
{$ifdef HASIMPLICITOPERATOR}
|
|
function GetV(position: integer): TDocValue;
|
|
{$endif HASIMPLICITOPERATOR}
|
|
procedure SetB(position: integer; value: boolean);
|
|
procedure SetC(position: integer; const value: currency);
|
|
procedure SetD(position: integer; const value: IDocDict);
|
|
procedure SetF(position: integer; const value: double);
|
|
procedure SetI(position: integer; value: Int64);
|
|
procedure SetItem(position: integer; const value: variant);
|
|
procedure SetL(position: integer; const value: IDocList);
|
|
procedure SetS(position: integer; const value: string);
|
|
procedure SetU(position: integer; const value: RawUtf8);
|
|
/// adds an element at the end of the list
|
|
function Append(const value: variant): integer; overload;
|
|
/// adds an UTF-8 text element at the end of the list
|
|
function Append(const value: RawUtf8): integer; overload;
|
|
/// adds an IDocList/IDocDict element at the end of the list
|
|
function AppendDoc(const value: IDocAny): integer;
|
|
/// compare this IDocList value with another instance
|
|
// - each element of the list will be compared, in their expected order
|
|
function Compare(const another: IDocList;
|
|
caseinsensitive: boolean = false): integer;
|
|
/// return a of a (sub-range) copy of this IDocList
|
|
// - consider Range() if you want just to loop over some sub-range, and
|
|
// do not need to allocate a new IDocList instance
|
|
// - returns a new IDocList instance as Python list[start:stop] range,
|
|
// stop position being *excluded* to the result:
|
|
// $ list.Copy returns a copy of the whole list
|
|
// $ list.Copy(10) returns elements #10..#Count-1, i.e. list[10:]
|
|
// $ list.Copy(-2) returns last #Count-2..#Count-1 items, i.e. list[-2:]
|
|
// $ list.Copy(1, 9) returns elements #1..#8, i.e. list[1..9]
|
|
// $ list.Copy(1, -2) returns elements #1..#Count-3, i.e. list[1:-2]
|
|
function Copy(start: integer = 0; stop: integer = 0): IDocList;
|
|
/// counts the number of elements with the specified value
|
|
function Count(const value: variant): integer; overload;
|
|
/// counts the number of elements with the specified value
|
|
function Count(const value: RawUtf8): integer; overload;
|
|
/// removes the element at the specified position, not returning it
|
|
function Del(position: integer): boolean;
|
|
/// check if a specified value is present in the list
|
|
function Exists(const value: variant): boolean; overload;
|
|
/// check if a specified text value is present in the list
|
|
function Exists(const value: RawUtf8;
|
|
caseinsensitive: boolean = false): boolean; overload;
|
|
/// add the elements of a list, to the end of the current list
|
|
procedure Extend(const value: IDocList); overload;
|
|
/// add the supplied elements, to the end of the current list
|
|
procedure Extend(const value: array of const); overload;
|
|
/// search matching expression over IDocDict kind of elements in this list
|
|
// - expressions are e.g. 'name=Synopse' or 'info.price<100'
|
|
function Filter(const expression: RawUtf8): IDocList; overload;
|
|
/// search matching expression over IDocDict kind of elements in this list
|
|
// - use e.g. Filter('name=', 'Synopse') or Filter('info.price<', MaxPrice)
|
|
function Filter(const expression: RawUtf8; const value: variant;
|
|
limit: integer = 0): IDocList; overload;
|
|
/// search matching key/value over IDocDict kind of elements in this list
|
|
// - raw search for compare(object.key,value)=match
|
|
// - default compare=nil will use VariantCompare
|
|
function Filter(const key: RawUtf8; const value: variant; limit: integer;
|
|
match: TCompareOperator; compare: TVariantCompare): IDocList; overload;
|
|
/// search the first matching expression over IDocDict kind of elements
|
|
function First(const expression: RawUtf8): variant; overload;
|
|
/// search the first matching expression over IDocDict kind of elements
|
|
function First(const expression: RawUtf8; const value: variant): variant; overload;
|
|
/// returns the position at the first occurrence of the specified value
|
|
function Index(const value: variant): integer; overload;
|
|
/// returns the position at the first occurrence of the specified text value
|
|
function Index(const value: RawUtf8;
|
|
caseinsensitive: boolean = false): integer; overload;
|
|
/// inserts the specified value at the specified position
|
|
function Insert(position: integer; const value: variant): integer; overload;
|
|
/// inserts the specified value at the specified position
|
|
function Insert(position: integer; const value: RawUtf8): integer; overload;
|
|
/// returns all IDocDict kind of elements of this IDocList
|
|
// - the list should consist e.g. of a JSON array of JSON objects
|
|
// - will just ignore any element of the IDocList which is not a IDocDict
|
|
function ObjectsDictDynArray: IDocDictDynArray;
|
|
/// removes the element at the specified position, and returns it
|
|
// - raise an EDocList on invalid supplied position
|
|
function Pop(position: integer = -1): variant;
|
|
/// removes the last inserted element into a value-owned IDocDict
|
|
// - returns false if there is no value at this position (raise no EDocList)
|
|
// - you may change the extraction position (negatives from Len)
|
|
function PopItem(out value: variant; position: integer = -1): boolean; overload;
|
|
/// removes the last inserted IDocDict, with no out-of-range error raised
|
|
// - you may change the extraction position (negatives from Len)
|
|
// - returns false if there is no value at this position, or it is no IDocDict
|
|
function PopItem(out value: IDocDict; position: integer = -1): boolean; overload;
|
|
/// extract a list of IDocDict elements which contains only specified keys
|
|
// - could be used to filter a list of objects into a smaller dataset
|
|
function Reduce(const keys: array of RawUtf8): IDocList;
|
|
/// removes the first occurrence of the element with the specified value
|
|
function Remove(const value: variant): integer; overload;
|
|
/// removes the first occurrence of the element with the specified value
|
|
function Remove(const value: RawUtf8;
|
|
caseinsensitive: boolean = false): integer; overload;
|
|
/// reverses the sorting order of the elements
|
|
procedure Reverse;
|
|
/// sorts the list ascending by default
|
|
procedure Sort(reverse: boolean = false; compare: TVariantCompare = nil);
|
|
/// sorts the IDocDict objects in the list by a given key name
|
|
procedure SortByKeyValue(const key: RawUtf8; reverse: boolean = false;
|
|
valuecompare: TVariantCompare = nil); overload;
|
|
/// sorts the IDocDict objects in the list by several key names
|
|
procedure SortByKeyValue(const keys: array of RawUtf8;
|
|
reverse: boolean = false; valuecompare: TVariantCompare = nil); overload;
|
|
/// low-level direct access to a stored element in TDocVariantData.Value[]
|
|
function ValueAt(position: integer): PVariant;
|
|
{$ifdef HASIMPLICITOPERATOR}
|
|
/// default iterator over all elements of this IDocList, returning TDocValue
|
|
// - warning: weak reference to the main list, unless you explicitly Copy it
|
|
function GetEnumerator: TDocValueEnumerator;
|
|
/// allow to iterate over a specific range of elements of this IDocList
|
|
// - elements are returned directly from the main list as weak references
|
|
// $ var v: TDocValue;
|
|
// $ "for v in list.Range" returns all elements, i.e. maps "for v in list"
|
|
// $ "for v in list.Range(10)" returns #10..#Count-1, i.e. list[10:]
|
|
// $ "for v in list.Range(-2)" returns last #Count-2..#Count-1, i.e. list[-2:]
|
|
// $ "for v in list.Range(1, 9)" returns #1..#8, i.e. list[1..9]
|
|
// $ "for v in list.Range(1, -2)" returns #1..#Count-3, i.e. list[1:-2]
|
|
function Range(start: integer = 0; stop: integer = 0): TDocValueEnumerator;
|
|
/// iterate over IDocDict kind of elements in this IDocList
|
|
// - the list should consist e.g. of a JSON array of JSON objects
|
|
// - will just ignore any element of the IDocList which is not a IDocDict
|
|
// - TDocObjectEnumerator will maintain and reuse a single IDocDict instance
|
|
// - warning: IDocDict is a weak reference, explicitly Copy outside the loop
|
|
function Objects: TDocObjectEnumerator; overload;
|
|
/// iterate matching expression over IDocDict kind of elements in this IDocList
|
|
// - expressions are e.g. 'name=Synopse' or 'info.price<100'
|
|
// - warning: IDocDict is a weak reference, explicitly Copy outside the loop
|
|
function Objects(const expression: RawUtf8): TDocObjectEnumerator; overload;
|
|
/// iterate matching expression over IDocDict kind of elements in this IDocList
|
|
// - use e.g. Objects('name =', 'Synopse') or Objects('info.price<', MaxPrice)
|
|
// - warning: IDocDict is a weak reference, explicitly Copy outside the loop
|
|
function Objects(const expression: RawUtf8;
|
|
const value: variant): TDocObjectEnumerator; overload;
|
|
/// iterate matching key/value over IDocDict kind of elements in this IDocList
|
|
// - raw search for compare(object.key,value)=match
|
|
// - warning: IDocDict is a weak reference, explicitly Copy outside the loop
|
|
function Objects(const key: RawUtf8; const value: variant;
|
|
match: TCompareOperator; compare: TVariantCompare): TDocObjectEnumerator; overload;
|
|
/// read access of one element in the list, as TDocValue
|
|
// - may be more convenient than the plain variant as returned by Item[]
|
|
// - warning: weak reference to the main list, unless you explicitly Copy it
|
|
property V[position: integer]: TDocValue
|
|
read GetV;
|
|
{$endif HASIMPLICITOPERATOR}
|
|
/// access one element in the list, as variant
|
|
// - this is the default property of this instance so list[n] gives
|
|
// direct access to the element at index 0 <= n < len -1 in the IDocList
|
|
// - negative positions are retrieved from the end, e.g. list[-1] maps
|
|
// the last element of the list
|
|
// - raise an EDocList exception on out-of-range position
|
|
// - U[] S[] I[] F[] C[] B[] L[] D[] are faster and safer if you expect
|
|
// to retrieve a specific value type
|
|
property Item[position: integer]: variant
|
|
read GetItem write SetItem; default;
|
|
/// access one element in the list, as UTF-8 text
|
|
property U[position: integer]: RawUtf8
|
|
read GetU write SetU;
|
|
/// access one element in the list, as RTL String text
|
|
property S[position: integer]: string
|
|
read GetS write SetS;
|
|
/// access one element in the list, as Integer
|
|
property I[position: integer]: Int64
|
|
read GetI write SetI;
|
|
/// access one element in the list, as floating-point Double
|
|
property F[position: integer]: double
|
|
read GetF write SetF;
|
|
/// access one element in the list, as fixed precision Currency
|
|
property C[position: integer]: currency
|
|
read GetC write SetC;
|
|
/// access one element in the list, as boolean
|
|
property B[position: integer]: boolean
|
|
read GetB write SetB;
|
|
/// access one element in the list, as IDocList (List)
|
|
// - warning: weak reference to the main list, unless you explicitly Copy it
|
|
property L[position: integer]: IDocList
|
|
read GetL write SetL;
|
|
/// access one element in the list, as IDocDict (Dictionary)
|
|
// - warning: weak reference to the main list, unless you explicitly Copy it
|
|
property D[position: integer]: IDocDict
|
|
read GetD write SetD;
|
|
/// access one element in the list, as IDocList/IDocArray
|
|
// - property alias, for compatibility with existing code
|
|
// - warning: weak reference to the main list, unless you explicitly Copy it
|
|
property A[position: integer]: IDocList
|
|
read GetL write SetL;
|
|
/// access one element in the list, as IDocDict/IDocObject
|
|
// - property alias, for compatibility with existing code
|
|
// - warning: weak reference to the main list, unless you explicitly Copy it
|
|
property O[position: integer]: IDocDict
|
|
read GetD write SetD;
|
|
end;
|
|
|
|
/// a Dictionary, used to store key:value pairs
|
|
// - implemented via an internal TDocVariantData dvObject
|
|
// - follows most Python Dictionaries naming and conventions
|
|
// - keys are by default searched on the ground object, but you can set
|
|
// PathDelim e.g. to '.' to enable sub-object location (and insertion)
|
|
IDocDict = interface(IDocAny)
|
|
['{E4176BFF-AFDC-4A63-8DBD-D4F495D56B1A}']
|
|
// methods used as getter/setter for properties
|
|
function GetB(const key: RawUtf8): boolean;
|
|
function GetC(const key: RawUtf8): currency;
|
|
function GetD(const key: RawUtf8): IDocDict;
|
|
function GetF(const key: RawUtf8): double;
|
|
function GetI(const key: RawUtf8): Int64;
|
|
function GetItem(const key: RawUtf8): variant;
|
|
function GetL(const key: RawUtf8): IDocList;
|
|
function GetS(const key: RawUtf8): string;
|
|
function GetU(const key: RawUtf8): RawUtf8;
|
|
{$ifdef HASIMPLICITOPERATOR}
|
|
function GetV(const key: RawUtf8): TDocValue;
|
|
{$endif HASIMPLICITOPERATOR}
|
|
procedure SetB(const key: RawUtf8; const value: boolean);
|
|
procedure SetC(const key: RawUtf8; const value: currency);
|
|
procedure SetD(const key: RawUtf8; const value: IDocDict);
|
|
procedure SetF(const key: RawUtf8; const value: double);
|
|
procedure SetI(const key: RawUtf8; const value: Int64);
|
|
procedure SetItem(const key: RawUtf8; const value: variant);
|
|
procedure SetL(const key: RawUtf8; const value: IDocList);
|
|
procedure SetS(const key: RawUtf8; const value: string);
|
|
procedure SetU(const key: RawUtf8; const value: RawUtf8);
|
|
function GetPathDelim: AnsiChar;
|
|
procedure SetPathDelim(value: AnsiChar);
|
|
/// compare this IDocDict value with another instance
|
|
// - each key/value pair will be compared, in their expected order
|
|
function Compare(const another: IDocDict;
|
|
caseinsensitive: boolean = false): integer; overload;
|
|
/// compare the specified keys of this IDocDict value with another instance
|
|
function Compare(const another: IDocDict; const keys: array of RawUtf8;
|
|
caseinsensitive: boolean = false): integer; overload;
|
|
/// returns a copy of the specified dictionary
|
|
function Copy: IDocDict;
|
|
/// removes the element at the specified key, not returning it
|
|
function Del(const key: RawUtf8): boolean;
|
|
/// check if the specified key exists in the dictionary
|
|
function Exists(const key: RawUtf8): boolean;
|
|
/// access one element in the dictionary, as variant
|
|
// - if the key does not exist, returns varEmpty and raise no exception
|
|
function Get(const key: RawUtf8): variant; overload;
|
|
/// access one element in the dictionary, as variant
|
|
// - if the key does not exist, returns the supplied default value
|
|
function GetDef(const key: RawUtf8; const default: variant): variant; overload;
|
|
/// access one element in the dictionary, as variant
|
|
// - if the key does not exist, returns the supplied default value
|
|
function GetDef(const key: RawUtf8; const default: RawUtf8): variant; overload;
|
|
/// access one element in the dictionary, as variant
|
|
// - if the key does not exist, returns false
|
|
function Get(const key: RawUtf8; var value: variant): boolean; overload;
|
|
/// access one element in the dictionary, as UTF-8 text
|
|
// - if the key does not exist, returns false
|
|
function Get(const key: RawUtf8; var value: RawUtf8): boolean; overload;
|
|
/// access one element in the dictionary, as RTL string text
|
|
// - if the key does not exist, returns false
|
|
function Get(const key: RawUtf8; var value: string): boolean; overload;
|
|
/// access one element in the dictionary, as boolean
|
|
// - if the key does not exist or is not an integer, returns false
|
|
function Get(const key: RawUtf8; var value: boolean): boolean; overload;
|
|
/// access one element in the dictionary, as 32-bit integer
|
|
// - if the key does not exist or is not an integer, returns false
|
|
function Get(const key: RawUtf8; var value: integer): boolean; overload;
|
|
/// access one element in the dictionary, as 64-bit integer
|
|
// - if the key does not exist or is not an integer, returns false
|
|
function Get(const key: RawUtf8; var value: Int64): boolean; overload;
|
|
/// access one element in the dictionary, as floating-point double
|
|
// - if the key does not exist or can not be converted, returns false
|
|
function Get(const key: RawUtf8; var value: double): boolean; overload;
|
|
/// access one element in the dictionary, fixed precision currency
|
|
// - if the key does not exist or can not be converted, returns false
|
|
function Get(const key: RawUtf8; var value: currency): boolean; overload;
|
|
/// access one element in the dictionary, as by-reference IDocList
|
|
// - if the key does not exist or is not a IDocList, returns false
|
|
function Get(const key: RawUtf8; var value: IDocList): boolean; overload;
|
|
/// access one element in the dictionary, as a by-reference IDocDict
|
|
// - if the key does not exist or is not a IDocDict, returns false
|
|
function Get(const key: RawUtf8; var value: IDocDict): boolean; overload;
|
|
/// access one element in the dictionary, as by-reference TDocVariantData
|
|
// - if the key does not exist or is not a IDocList, returns false
|
|
function Get(const key: RawUtf8; var value: PDocVariantData): boolean; overload;
|
|
/// removes the specified element from the dictionary
|
|
// - if the key does not exist, raise a EDocDict exception
|
|
function Pop(const key: RawUtf8): variant; overload;
|
|
/// removes the specified element from the dictionary
|
|
// - if the key does not exist, returns the supplied default value
|
|
function Pop(const key: RawUtf8; const default: variant): variant; overload;
|
|
/// removes the last inserted key-value pair into the dictionary
|
|
// - returns false if the dictionary is empty
|
|
// - you may change the extraction position (negatives from Len)
|
|
function PopItem(out key: RawUtf8; out value: variant;
|
|
position: integer = -1): boolean;
|
|
/// extract into a new IDocDict which contains only specified keys
|
|
// - could be used to filter unneeded fields in an object
|
|
function Reduce(const keys: array of RawUtf8): IDocDict;
|
|
/// returns the value of the specified key, or insert null for this key
|
|
function SetDefault(const key: RawUtf8): variant; overload;
|
|
/// returns the value of the specified key, or insert the specified value
|
|
function SetDefault(const key: RawUtf8; const default: variant): variant; overload;
|
|
/// sorts the dictionary content by their key names
|
|
// - follow dvoNameCaseSensitive option by default, or supplied keycompare
|
|
// - once sorted, key lookup will use O(log(n)) - faster than default O(n)
|
|
procedure Sort(reverse: boolean = false; keycompare: TUtf8Compare = nil);
|
|
/// updates (or inserts) the specified key/value pair
|
|
procedure Update(const key: RawUtf8; const value: variant); overload;
|
|
/// updates (or inserts) the specified key/value pairs
|
|
procedure Update(const keyvalues: array of const); overload;
|
|
/// updates (or inserts) the specified key/value pairs of another IDocDict
|
|
procedure Update(const source: IDocDict; addonlymissing: boolean = false); overload;
|
|
/// low-level direct access to a stored element in TDocVariantData.Value[]
|
|
function ValueAt(const key: RawUtf8): PVariant;
|
|
{$ifdef HASIMPLICITOPERATOR}
|
|
/// default iterator over all key/value pairs of this IDocDict
|
|
function GetEnumerator: TDocDictEnumerator;
|
|
/// allow to iterate over all keys of this IDocDict
|
|
function Keys: TDocKeyEnumerator;
|
|
/// allow to iterate over all values of this IDocDict
|
|
function Values: TDocValueEnumerator;
|
|
/// read access of one element in the dictionary from its key, as TDocValue
|
|
// - may be slightly more convenient than the plain variant returned by Item[]
|
|
property V[const key: RawUtf8]: TDocValue
|
|
read GetV;
|
|
{$endif HASIMPLICITOPERATOR}
|
|
/// enable nested objects location in keys for Get() and Item[]
|
|
// - equals #0 by default, meaning only root object keys are located, e.g.
|
|
// dict.D['child2']
|
|
// - if PathDelim is e.g. set to '.', then dict.U['child2.name'] matches
|
|
// dict.D['child2'].U['name']
|
|
// - note that if the sub object does not exist, setting a value will force
|
|
// its creation (and all needed hierarchy)
|
|
property PathDelim: AnsiChar
|
|
read GetPathDelim write SetPathDelim;
|
|
/// access one element in the dictionary from its key, as variant
|
|
// - this is the default property of this instance so dict[name] gives
|
|
// direct access to each value in the IDocDict
|
|
// - follows dvoNameCaseSensitive and dvoReturnNullForUnknownProperty options
|
|
// - see Get() overloaded methods to silently check for a key existence
|
|
// - PathDelim can be set to locate (and create) the key by its full path
|
|
// - U[] S[] I[] F[] C[] B[] L[] D[] are faster and safer if you expect
|
|
// to retrieve a specific value type
|
|
property Item[const key: RawUtf8]: variant
|
|
read GetItem write SetItem; default;
|
|
/// access one element in the dictionary from its key, as UTF-8 text
|
|
property U[const key: RawUtf8]: RawUtf8
|
|
read GetU write SetU;
|
|
/// access one element in the dictionary from its key, as RTL string text
|
|
property S[const key: RawUtf8]: string
|
|
read GetS write SetS;
|
|
/// access one element in the dictionary from its key, as integer
|
|
property I[const key: RawUtf8]: Int64
|
|
read GetI write SetI;
|
|
/// access one element in the dictionary from its key, as floating-point double
|
|
property F[const key: RawUtf8]: double
|
|
read GetF write SetF;
|
|
/// access one element in the dictionary from its key, as fixed precision currency
|
|
property C[const key: RawUtf8]: currency
|
|
read GetC write SetC;
|
|
/// access one element in the dictionary from its key, as boolean
|
|
property B[const key: RawUtf8]: boolean
|
|
read GetB write SetB;
|
|
/// access one element in the dictionary from its key, as IDocList
|
|
property L[const key: RawUtf8]: IDocList
|
|
read GetL write SetL;
|
|
/// access one element in the dictionary from its key, as IDocDict
|
|
property D[const key: RawUtf8]: IDocDict
|
|
read GetD write SetD;
|
|
/// access one element in the dictionary from its key, as IDocList/IDocArray
|
|
// - property alias, for compatibility with existing code
|
|
property A[const key: RawUtf8]: IDocList
|
|
read GetL write SetL;
|
|
/// access one element in the dictionary from its key, as IDocDict/IDocObject
|
|
// - property alias, for compatibility with existing code
|
|
property O[const key: RawUtf8]: IDocDict
|
|
read GetD write SetD;
|
|
end;
|
|
|
|
/// alias to our interface list type, for compatibility with existing code
|
|
IDocArray = IDocList;
|
|
/// alias to our interface dictionary type, for compatibility with existing code
|
|
IDocObject = IDocDict;
|
|
|
|
|
|
/// create a self-owned void IDocList
|
|
function DocList(model: TDocVariantModel = mFastFloat): IDocList; overload;
|
|
|
|
/// create a self-owned IDocList from a JSON array
|
|
function DocList(const json: RawUtf8;
|
|
model: TDocVariantModel = mFastFloat): IDocList; overload;
|
|
|
|
/// create a self-owned IDocList from TOrmTableJson ORM/DB dual JSON formats
|
|
function DocListFromResults(const json: RawUtf8;
|
|
model: TDocVariantModel = mFastFloat): IDocList;
|
|
|
|
/// create a self-owned IDocList from a set of values
|
|
function DocList(const values: array of const;
|
|
model: TDocVariantModel = mFastFloat): IDocList; overload;
|
|
|
|
/// create a IDocList as weak reference to a TDocVariant dvArray
|
|
function DocListFrom(const v: variant): IDocList; overload;
|
|
|
|
/// create a self-owned IDocList from a dynamic array of IDocDict values
|
|
function DocListFrom(const dictarray: IDocDictDynArray): IDocList; overload;
|
|
|
|
/// create a IDocList as weak reference to a TDocVariantData dvArray
|
|
function DocList(const dv: TDocVariantData): IDocList; overload;
|
|
|
|
/// create a self-owned IDocList as full copy of a TDocVariantData dvArray
|
|
function DocListCopy(const dv: TDocVariantData): IDocList; overload;
|
|
|
|
/// create a self-owned IDocList as full copy of a TDocVariant dvArray
|
|
function DocListCopy(const v: variant): IDocList; overload;
|
|
|
|
/// create a self-owned IDocList as full copy of a TDocVariantData dvArray
|
|
// and a specific options model
|
|
function DocListCopy(const dv: TDocVariantData;
|
|
model: TDocVariantModel): IDocList; overload;
|
|
|
|
|
|
/// create a self-owned void IDocDict
|
|
function DocDict(model: TDocVariantModel = mFastFloat): IDocDict; overload;
|
|
|
|
/// create a self-owned IDocDict from a JSON object
|
|
function DocDict(const json: RawUtf8;
|
|
model: TDocVariantModel = mFastFloat): IDocDict; overload;
|
|
|
|
/// create an array of self-owned IDocDict from a JSON array of JSON objects
|
|
// - set jsonfromresults=true if input is TOrmTableJson ORM/DB dual JSON formats
|
|
// - any element of the JSON array which is not a JSON object will be ignored
|
|
function DocDictDynArray(const json: RawUtf8;
|
|
model: TDocVariantModel = mFastFloat;
|
|
jsonfromresults: boolean = false): IDocDictDynArray;
|
|
|
|
/// create a self-owned IDocDict from a set of key,value pairs
|
|
function DocDict(const keyvalues: array of const;
|
|
model: TDocVariantModel = mFastFloat): IDocDict; overload;
|
|
|
|
/// create a self-owned IDocDict from a set of keys - values will be Null
|
|
function DocDictFromKeys(const keys: array of RawUtf8;
|
|
model: TDocVariantModel = mFastFloat): IDocDict; overload;
|
|
|
|
/// create a self-owned IDocDict from a set of keys and a gien value
|
|
function DocDictFromKeys(const keys: array of RawUtf8; const value: variant;
|
|
model: TDocVariantModel = mFastFloat): IDocDict; overload;
|
|
|
|
/// create a IDocDict as weak reference to a TDocVariant dvObject
|
|
function DocDictFrom(const v: variant): IDocDict;
|
|
|
|
/// create a IDocDict as weak reference to a TDocVariantData dvObject
|
|
function DocDict(const dv: TDocVariantData): IDocDict; overload;
|
|
|
|
/// create a self-owned IDocDict as full copy of a TDocVariantData dvObject
|
|
function DocDictCopy(const dv: TDocVariantData): IDocDict; overload;
|
|
|
|
/// create a self-owned IDocDict as full copy of a TDocVariant dvObject
|
|
function DocDictCopy(const v: variant): IDocDict; overload;
|
|
|
|
/// create a self-owned IDocDict as full copy of a TDocVariantData dvObject
|
|
// and a specific options model
|
|
function DocDictCopy(const dv: TDocVariantData;
|
|
model: TDocVariantModel): IDocDict; overload;
|
|
|
|
var
|
|
/// default TDocVariant model for IDocList/IDocDict
|
|
DocAnyDefaultModel: TDocVariantModel = mFastFloat;
|
|
|
|
/// internal initialization function called from mormot.core.json
|
|
procedure InitializeVariantsJson;
|
|
|
|
|
|
implementation
|
|
|
|
// some early methods implementation, defined here for proper inlining
|
|
// 32-bit PInteger() is faster than 16-bit (dvoXXX in VOptions) on Intel CPUs
|
|
|
|
const
|
|
_DVO = 1 shl ord(dvoIsArray) + 1 shl ord(dvoIsObject);
|
|
|
|
function TDocVariantData.GetKind: TDocVariantKind;
|
|
begin // [dvoIsArray]=1 [dvoIsObject]=2 -> dvUndefined=0 dvArray=1 dvObject=2
|
|
result := TDocVariantKind((TRttiVarData(self).VType shr 16) and _DVO);
|
|
end;
|
|
|
|
function TDocVariantData.Has(dvo: TDocVariantOption): boolean;
|
|
begin
|
|
result := (TRttiVarData(self).VType and (1 shl (ord(dvo) + 16))) <> 0;
|
|
end;
|
|
|
|
function TDocVariantData.IsObject: boolean;
|
|
begin
|
|
result := Has(dvoIsObject);
|
|
end;
|
|
|
|
function TDocVariantData.IsArray: boolean;
|
|
begin
|
|
result := Has(dvoIsArray);
|
|
end;
|
|
|
|
function TDocVariantData.IsCaseSensitive: boolean;
|
|
begin
|
|
result := Has(dvoNameCaseSensitive);
|
|
end;
|
|
|
|
procedure TDocVariantData.ClearFast;
|
|
begin
|
|
TRttiVarData(self).VType := 0; // clear VType and VOptions
|
|
Void;
|
|
end;
|
|
|
|
procedure TDocVariantData.InternalSetValue(aIndex: PtrInt; const aValue: variant);
|
|
begin
|
|
SetVariantByValue(aValue, VValue[aIndex]); // caller ensured that aIndex is OK
|
|
if Has(dvoInternValues) then
|
|
InternalUniqueValueAt(aIndex);
|
|
end;
|
|
|
|
|
|
{ ************** Low-Level Variant Wrappers }
|
|
|
|
function VarIs(const V: Variant; const VTypes: TVarDataTypes): boolean;
|
|
var
|
|
vd: PVarData;
|
|
vt: cardinal;
|
|
begin
|
|
vd := @V;
|
|
repeat
|
|
vt := vd^.VType;
|
|
if vt <> varVariantByRef then
|
|
break;
|
|
vd := vd^.VPointer;
|
|
if vd = nil then
|
|
begin
|
|
result := false;
|
|
exit;
|
|
end;
|
|
until false;
|
|
result := vt in VTypes;
|
|
end;
|
|
|
|
function VarIsVoid(const V: Variant): boolean;
|
|
var
|
|
vt: cardinal;
|
|
custom: TSynInvokeableVariantType;
|
|
begin
|
|
vt := TVarData(V).VType;
|
|
with TVarData(V) do
|
|
case vt of
|
|
varEmpty,
|
|
varNull:
|
|
result := true;
|
|
varBoolean:
|
|
result := not VBoolean;
|
|
{$ifdef HASVARUSTRING}
|
|
varUString,
|
|
{$endif HASVARUSTRING}
|
|
varString,
|
|
varOleStr:
|
|
result := VAny = nil;
|
|
varDate:
|
|
result := VInt64 = 0;
|
|
// note: 0 as integer or float is considered as non-void
|
|
else
|
|
if vt = varVariantByRef then
|
|
result := VarIsVoid(PVariant(VPointer)^)
|
|
else if (vt = varStringByRef) or
|
|
(vt = varOleStrByRef)
|
|
{$ifdef HASVARUSTRING} or
|
|
(vt = varUStringByRef)
|
|
{$endif HASVARUSTRING} then
|
|
result := PPointer(VAny)^ = nil
|
|
else if vt = DocVariantVType then
|
|
result := TDocVariantData(V).Count = 0
|
|
else
|
|
begin
|
|
custom := FindSynVariantType(vt);
|
|
result := (custom <> nil) and
|
|
custom.IsVoid(TVarData(V)); // e.g. TBsonVariant.IsVoid
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function VarStringOrNull(const v: RawUtf8): variant;
|
|
begin
|
|
if v = '' then
|
|
SetVariantNull(result{%H-})
|
|
else
|
|
RawUtf8ToVariant(v, result);
|
|
end;
|
|
|
|
procedure SetVariantByRef(const Source: Variant; var Dest: Variant);
|
|
var
|
|
vt: cardinal;
|
|
begin
|
|
if PInteger(@Dest)^ <> 0 then // VarClear() is not always inlined :(
|
|
VarClear(Dest);
|
|
vt := TVarData(Source).VType;
|
|
if ((vt and varByRef) <> 0) or
|
|
(vt in VTYPE_SIMPLE) then
|
|
TVarData(Dest) := TVarData(Source)
|
|
else if not SetVariantUnRefSimpleValue(Source, TVarData(Dest)) then
|
|
begin
|
|
TRttiVarData(Dest).VType := varVariantByRef;
|
|
TVarData(Dest).VPointer := @Source;
|
|
end;
|
|
end;
|
|
|
|
procedure SetVariantByValue(const Source: Variant; var Dest: Variant);
|
|
var
|
|
s: PVarData;
|
|
d: TVarData absolute Dest;
|
|
dt: cardinal absolute Dest;
|
|
vt: cardinal;
|
|
ct: TSynInvokeableVariantType;
|
|
begin
|
|
s := @Source;
|
|
if PInteger(@Dest)^ <> 0 then // VarClear() is not always inlined :(
|
|
VarClear(Dest);
|
|
vt := s^.VType;
|
|
while vt = varVariantByRef do
|
|
begin
|
|
s := s^.VPointer;
|
|
vt := s^.VType;
|
|
end;
|
|
case vt of
|
|
varEmpty..varDate,
|
|
varBoolean,
|
|
varShortInt..varWord64:
|
|
begin
|
|
dt := vt;
|
|
d.VInt64 := s^.VInt64;
|
|
end;
|
|
varString:
|
|
begin
|
|
dt := varString;
|
|
d.VAny := nil;
|
|
RawByteString(d.VAny) := RawByteString(s^.VAny);
|
|
end;
|
|
varStringByRef:
|
|
begin
|
|
dt := varString;
|
|
d.VAny := nil;
|
|
RawByteString(d.VAny) := PRawByteString(s^.VAny)^;
|
|
end;
|
|
{$ifdef HASVARUSTRING}
|
|
varUString,
|
|
varUStringByRef,
|
|
{$endif HASVARUSTRING}
|
|
varOleStr,
|
|
varOleStrByRef:
|
|
begin
|
|
dt := varString;
|
|
d.VAny := nil;
|
|
VariantToUtf8(PVariant(s)^, RawUtf8(d.VAny)); // store as RawUtf8
|
|
end;
|
|
else // note: varVariant should not happen here
|
|
if DocVariantType.FindSynVariantType(vt, ct) then
|
|
ct.CopyByValue(d, s^) // needed e.g. for TBsonVariant
|
|
else
|
|
SetVariantUnRefSimpleValue(PVariant(s)^, d);
|
|
end;
|
|
end;
|
|
|
|
procedure ZeroFill(Value: PVarData);
|
|
begin
|
|
// slightly faster than FillChar(Value,SizeOf(Value),0);
|
|
PInt64Array(Value)^[0] := 0;
|
|
PInt64Array(Value)^[1] := 0;
|
|
{$ifdef CPU64}
|
|
PInt64Array(Value)^[2] := 0;
|
|
{$endif CPU64}
|
|
end;
|
|
|
|
procedure FillZero(var value: variant);
|
|
begin
|
|
if TRttiVarData(value).VType and $ffff = varString then
|
|
FillZero(RawByteString(TVarData(value).VAny));
|
|
VarClear(value);
|
|
end;
|
|
|
|
procedure _VariantClearSeveral(V: PVarData; n: integer);
|
|
var
|
|
vt, docv: cardinal;
|
|
handler: TCustomVariantType;
|
|
clearproc: procedure(V: PVarData);
|
|
label
|
|
clr, hdr;
|
|
begin
|
|
handler := nil;
|
|
docv := DocVariantVType;
|
|
clearproc := @VarClearProc;
|
|
repeat
|
|
vt := V^.VType;
|
|
if vt <= varWord64 then
|
|
begin
|
|
if (vt >= varOleStr) and
|
|
(vt <= varError) then
|
|
if vt = varOleStr then
|
|
WideString(V^.VAny) := ''
|
|
else
|
|
goto clr; // varError/varDispatch
|
|
end // note: varVariant/varUnknown are not handled because should not appear
|
|
else if vt = varString then
|
|
{$ifdef FPC}
|
|
FastAssignNew(V^.VAny)
|
|
{$else}
|
|
RawUtf8(V^.VAny) := ''
|
|
{$endif FPC}
|
|
else if vt < varByRef then // varByRef has no refcount -> nothing to clear
|
|
if vt = docv then
|
|
PDocVariantData(V)^.ClearFast // faster than Clear
|
|
{$ifdef HASVARUSTRING}
|
|
else if vt = varUString then
|
|
UnicodeString(V^.VAny) := ''
|
|
{$endif HASVARUSTRING}
|
|
else if vt >= varArray then // custom types are below varArray
|
|
clr: clearproc(V)
|
|
else if handler = nil then
|
|
if FindCustomVariantType(vt, handler) then
|
|
hdr: handler.Clear(V^)
|
|
else
|
|
goto clr
|
|
else if vt = handler.VarType then
|
|
goto hdr
|
|
else
|
|
goto clr;
|
|
PInteger(V)^ := varEmpty; // reset VType
|
|
inc(V);
|
|
dec(n);
|
|
until n = 0;
|
|
end;
|
|
|
|
procedure RawUtf8ToVariant(const Txt: RawUtf8; var Value: TVarData;
|
|
ExpectedValueType: cardinal);
|
|
begin
|
|
if ExpectedValueType = varString then
|
|
begin
|
|
RawUtf8ToVariant(Txt, variant(Value));
|
|
exit;
|
|
end;
|
|
VarClearAndSetType(variant(Value), ExpectedValueType);
|
|
Value.VAny := nil; // avoid GPF below
|
|
if Txt <> '' then
|
|
case ExpectedValueType of
|
|
varOleStr:
|
|
Utf8ToWideString(Txt, WideString(Value.VAny));
|
|
{$ifdef HASVARUSTRING}
|
|
varUString:
|
|
Utf8DecodeToUnicodeString(
|
|
pointer(Txt), length(Txt), UnicodeString(Value.VAny));
|
|
{$endif HASVARUSTRING}
|
|
else
|
|
raise ESynVariant.CreateUtf8('RawUtf8ToVariant(%)?', [ExpectedValueType]);
|
|
end;
|
|
end;
|
|
|
|
function VariantToString(const V: Variant): string;
|
|
begin
|
|
VariantToString(V, result);
|
|
end;
|
|
|
|
procedure VariantToString(const V: Variant; var result: string);
|
|
var
|
|
wasString: boolean;
|
|
tmp: RawUtf8;
|
|
vt: cardinal;
|
|
begin
|
|
vt := TVarData(V).VType;
|
|
case vt of
|
|
varEmpty,
|
|
varNull:
|
|
result := ''; // default VariantToUtf8(null)='null'
|
|
{$ifdef UNICODE} // not HASVARUSTRING: here we handle string=UnicodeString
|
|
varOleStr:
|
|
SetString(result, PWideChar(TVarData(V).VAny), length(WideString(TVarData(V).VAny)));
|
|
varUString:
|
|
result := UnicodeString(TVarData(V).VAny);
|
|
varUStringByRef:
|
|
result := PUnicodeString(TVarData(V).VAny)^;
|
|
varOleStrByRef:
|
|
SetString(result, PPWideChar(TVarData(V).VAny)^,
|
|
length(PWideString(TVarData(V).VAny)^));
|
|
{$endif UNICODE}
|
|
else
|
|
begin
|
|
VariantToUtf8(V, tmp, wasString);
|
|
if tmp = '' then
|
|
result := ''
|
|
else
|
|
{$ifndef UNICODE}
|
|
if not wasString or
|
|
(Unicode_CodePage = CP_UTF8) or
|
|
IsAnsiCompatible(tmp) then
|
|
result := tmp
|
|
else
|
|
{$endif UNICODE}
|
|
Utf8ToStringVar(tmp, result);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure VariantToVarRec(const V: variant; var result: TVarRec);
|
|
begin
|
|
result.VType := vtVariant;
|
|
if TVarData(V).VType = varVariantByRef then
|
|
result.VVariant := TVarData(V).VPointer
|
|
else
|
|
result.VVariant := @V;
|
|
end;
|
|
|
|
procedure VariantsToArrayOfConst(const V: array of variant; VCount: PtrInt;
|
|
out result: TTVarRecDynArray);
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
SetLength(result, VCount);
|
|
for i := 0 to VCount - 1 do
|
|
VariantToVarRec(V[i], result[i]);
|
|
end;
|
|
|
|
function VariantsToArrayOfConst(const V: array of variant): TTVarRecDynArray;
|
|
begin
|
|
VariantsToArrayOfConst(V, length(V), result);
|
|
end;
|
|
|
|
function RawUtf8DynArrayToArrayOfConst(const V: array of RawUtf8): TTVarRecDynArray;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
result := nil;
|
|
SetLength(result, Length(V));
|
|
for i := 0 to Length(V) - 1 do
|
|
begin
|
|
result[i].VType := vtAnsiString;
|
|
result[i].VAnsiString := pointer(V[i]);
|
|
end;
|
|
end;
|
|
|
|
function VarRecToVariant(const V: TVarRec): variant;
|
|
begin
|
|
VarRecToVariant(V, result);
|
|
end;
|
|
|
|
procedure VarRecToVariant(const V: TVarRec; var result: variant);
|
|
begin
|
|
VarClear(result{%H-});
|
|
with TRttiVarData(result) do
|
|
case V.VType of
|
|
vtPointer:
|
|
VType := varNull;
|
|
vtBoolean:
|
|
begin
|
|
VType := varBoolean;
|
|
Data.VBoolean := V.VBoolean;
|
|
end;
|
|
vtInteger:
|
|
begin
|
|
VType := varInteger;
|
|
Data.VInteger := V.VInteger;
|
|
end;
|
|
vtInt64:
|
|
begin
|
|
VType := varInt64;
|
|
Data.VInt64 := V.VInt64^;
|
|
end;
|
|
{$ifdef FPC}
|
|
vtQWord:
|
|
begin
|
|
VType := varWord64;
|
|
Data.VQWord := V.VQWord^;
|
|
end;
|
|
{$endif FPC}
|
|
vtCurrency:
|
|
begin
|
|
VType := varCurrency;
|
|
Data.VInt64 := PInt64(V.VCurrency)^;
|
|
end;
|
|
vtExtended:
|
|
begin
|
|
VType := varDouble;
|
|
Data.VDouble := V.VExtended^;
|
|
end;
|
|
vtVariant:
|
|
result := V.VVariant^;
|
|
// warning: use varStringByRef makes GPF -> safe and fast refcount
|
|
vtAnsiString:
|
|
begin
|
|
VType := varString;
|
|
Data.VAny := nil;
|
|
RawByteString(Data.VAny) := RawByteString(V.VAnsiString);
|
|
end;
|
|
{$ifdef HASVARUSTRING}
|
|
vtUnicodeString,
|
|
{$endif HASVARUSTRING}
|
|
vtWideString,
|
|
vtString,
|
|
vtPChar,
|
|
vtChar,
|
|
vtWideChar,
|
|
vtClass:
|
|
begin
|
|
VType := varString;
|
|
Data.VString := nil; // avoid GPF on next line
|
|
VarRecToUtf8(V, RawUtf8(Data.VString)); // decode as new RawUtf8
|
|
end;
|
|
vtObject:
|
|
// class instance will be serialized as a TDocVariant
|
|
ObjectToVariant(V.VObject, result, [woDontStoreDefault]);
|
|
else
|
|
raise ESynVariant.CreateUtf8('Unhandled TVarRec.VType=%', [V.VType]);
|
|
end;
|
|
end;
|
|
|
|
function VariantDynArrayToJson(const V: TVariantDynArray): RawUtf8;
|
|
var
|
|
tmp: TDocVariantData;
|
|
begin
|
|
tmp.InitArrayFromVariants(V);
|
|
result := tmp.ToJson;
|
|
end;
|
|
|
|
function VariantDynArrayToRawUtf8DynArray(const V: TVariantDynArray): TRawUtf8DynArray;
|
|
var
|
|
i: PtrInt;
|
|
ws: boolean;
|
|
begin
|
|
result := nil;
|
|
if V = nil then
|
|
exit;
|
|
SetLength(result, length(V));
|
|
for i := 0 to length(V) - 1 do
|
|
VariantToUtf8(V[i], result[i], ws);
|
|
end;
|
|
|
|
function JsonToVariantDynArray(const Json: RawUtf8): TVariantDynArray;
|
|
var
|
|
tmp: TDocVariantData;
|
|
begin
|
|
tmp.InitJson(Json, JSON_FAST);
|
|
result := tmp.VValue;
|
|
if result <> nil then
|
|
DynArrayFakeLength(result, tmp.Count);
|
|
end;
|
|
|
|
function ValuesToVariantDynArray(const items: array of const): TVariantDynArray;
|
|
var
|
|
tmp: TDocVariantData;
|
|
begin
|
|
tmp.InitArray(items, JSON_FAST);
|
|
result := tmp.VValue;
|
|
if result <> nil then
|
|
DynArrayFakeLength(result, tmp.Count);
|
|
end;
|
|
|
|
|
|
function SortDynArrayEmptyNull(const A, B): integer;
|
|
begin
|
|
result := 0; // VType=varEmpty/varNull are always equal
|
|
end;
|
|
|
|
function SortDynArrayWordBoolean(const A, B): integer;
|
|
begin
|
|
if WordBool(A) then // normalize
|
|
if WordBool(B) then
|
|
result := 0
|
|
else
|
|
result := 1
|
|
else if WordBool(B) then
|
|
result := -1
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
const
|
|
_VARDATATEXT: array[0.. varWord64 + 5] of string[15] = (
|
|
'Empty', 'Null', 'SmallInt', 'Integer', 'Single', 'Double', 'Currency',
|
|
'Date', 'OleStr', 'Dispatch', 'Error', 'Boolean', 'Variant', 'Unknown',
|
|
'Decimal', '15', 'ShortInt', 'Byte', 'Word', 'LongWord', 'Int64', 'QWord',
|
|
'String', 'UString', 'Any', 'Array', 'DocVariant');
|
|
var
|
|
_VariantTypeNameAsInt: shortstring; // seldom called
|
|
|
|
function VariantTypeName(V: PVarData): PShortString;
|
|
var
|
|
vt: PtrUInt;
|
|
ct: TSynInvokeableVariantType;
|
|
tmp: TVarData;
|
|
begin
|
|
vt := V.VType;
|
|
if vt > varWord64 then
|
|
repeat
|
|
if SetVariantUnRefSimpleValue(PVariant(V)^, tmp{%H-}) then
|
|
begin
|
|
V := @tmp;
|
|
vt := tmp.VType;
|
|
if vt <= varWord64 then
|
|
break;
|
|
end;
|
|
case vt of
|
|
varStrArg,
|
|
varString,
|
|
varStringByRef:
|
|
vt := varWord64 + 1;
|
|
{$ifdef HASVARUSTRARG}
|
|
varUStrArg,
|
|
{$endif HASVARUSTRARG}
|
|
{$ifdef HASVARUSTRING}
|
|
varUString,
|
|
varUStringByRef:
|
|
vt := varWord64 + 2;
|
|
{$endif HASVARUSTRING}
|
|
varAny:
|
|
vt := varWord64 + 3;
|
|
varArray:
|
|
vt := varWord64 + 4;
|
|
varVariantByRef:
|
|
begin
|
|
result := VariantTypeName(V^.VPointer);
|
|
exit;
|
|
end;
|
|
else
|
|
if vt = DocVariantVType then
|
|
vt := varWord64 + 5
|
|
else
|
|
begin
|
|
ct := FindSynVariantType(vt);
|
|
if ct = nil then
|
|
begin
|
|
str(vt, _VariantTypeNameAsInt);
|
|
result := @_VariantTypeNameAsInt; // return VType as number
|
|
end
|
|
else
|
|
result := PPointer(PPtrInt(ct)^ + vmtClassName)^;
|
|
exit;
|
|
end
|
|
end;
|
|
break;
|
|
until false;
|
|
result := @_VARDATATEXT[vt];
|
|
end;
|
|
|
|
function VariantTypeName(const V: variant): PShortString;
|
|
begin
|
|
result := VariantTypeName(@V);
|
|
end;
|
|
|
|
const
|
|
_CMP2SORT: array[0..18] of TDynArraySortCompare = (
|
|
nil, // 0
|
|
SortDynArrayEmptyNull, // 1
|
|
SortDynArraySmallInt, // 2
|
|
SortDynArrayInteger, // 3
|
|
SortDynArraySingle, // 4
|
|
SortDynArrayDouble, // 5
|
|
SortDynArrayInt64, // 6
|
|
SortDynArrayDouble, // 7
|
|
SortDynArrayShortInt, // 8
|
|
SortDynArrayByte, // 9
|
|
SortDynArrayWord, // 10
|
|
SortDynArrayCardinal, // 11
|
|
SortDynArrayInt64, // 12
|
|
SortDynArrayQWord, // 13
|
|
SortDynArrayWordBoolean, // 14
|
|
{$ifdef CPUINTEL}
|
|
SortDynArrayAnsiString, // 15
|
|
{$else}
|
|
SortDynArrayRawByteString,
|
|
{$endif CPUINTEL}
|
|
SortDynArrayAnsiStringI, // 16
|
|
SortDynArrayUnicodeString, // 17
|
|
SortDynArrayUnicodeStringI); // 18
|
|
var
|
|
// FastVarDataComp() efficient lookup for per-VType comparison function
|
|
_VARDATACMP: array[0 .. $102 {varUString}, boolean] of byte; // _CMP2SORT[]
|
|
|
|
function FastVarDataComp(A, B: PVarData; caseInsensitive: boolean): integer;
|
|
var
|
|
at, bt, cmp2sort: PtrUInt;
|
|
ah, bh: TSynInvokeableVariantType;
|
|
begin
|
|
at := PtrUInt(A); // A=nil -> at=varEmpty
|
|
if at <> 0 then
|
|
repeat
|
|
at := PVarData(at)^.VType;
|
|
if at <> varVariantByRef then
|
|
break;
|
|
at := PtrUInt(A.VPointer);
|
|
A := pointer(at);
|
|
until at = 0;
|
|
bt := PtrUInt(B);
|
|
if bt <> 0 then
|
|
repeat
|
|
bt := PVarData(bt)^.VType;
|
|
if bt <> varVariantByRef then
|
|
break;
|
|
bt := PtrUInt(B.VPointer);
|
|
B := pointer(bt);
|
|
until bt = 0;
|
|
if at = bt then
|
|
// optimized comparison if A and B share the same type (most common case)
|
|
if at <= high(_VARDATACMP) then
|
|
begin
|
|
cmp2sort := _VARDATACMP[at, caseInsensitive];
|
|
if cmp2sort <> 0 then
|
|
result := _CMP2SORT[cmp2sort](A^.VAny, B^.VAny)
|
|
else
|
|
result := VariantCompSimple(PVariant(A)^, PVariant(B)^)
|
|
end
|
|
else if at = varStringByRef then
|
|
// e.g. from TRttiVarData / TRttiCustomProp.CompareValue
|
|
result := _CMP2SORT[_VARDATACMP[varString, caseInsensitive]](
|
|
PPointer(A^.VAny)^, PPointer(B^.VAny)^)
|
|
else if at = varSynUnicode or varByRef then
|
|
result := _CMP2SORT[_VARDATACMP[varSynUnicode, caseInsensitive]](
|
|
PPointer(A^.VAny)^, PPointer(B^.VAny)^)
|
|
else if at < varFirstCustom then
|
|
result := VariantCompSimple(PVariant(A)^, PVariant(B)^)
|
|
else if at = DocVariantVType then
|
|
// direct TDocVariantDat.VName/VValue comparison with no serialization
|
|
result := PDocVariantData(A)^.Compare(PDocVariantData(B)^, caseInsensitive)
|
|
else
|
|
begin
|
|
ah := FindSynVariantType(at);
|
|
if ah = nil then
|
|
// compare from custom types UTF-8 text representation/serialization
|
|
result := VariantCompAsText(A, B, caseInsensitive)
|
|
else
|
|
// use proper virtual comparison method
|
|
result := ah.IntCompare(A^, B^, caseInsensitive);
|
|
end
|
|
// A and B do not share the same type
|
|
else if (at <= varNull) or
|
|
(bt <= varNull) then
|
|
result := ord(at > varNull) - ord(bt > varNull)
|
|
else if (at < varString) and
|
|
(at <> varOleStr) and
|
|
(bt < varString) and
|
|
(bt <> varOleStr) then
|
|
result := VariantCompSimple(PVariant(A)^, PVariant(B)^)
|
|
else if (at < varFirstCustom) and
|
|
(bt < varFirstCustom) then
|
|
result := VariantCompAsText(A, B, caseInsensitive) // RawUtf8 convert
|
|
else
|
|
begin
|
|
ah := FindSynVariantType(at);
|
|
bh := FindSynVariantType(bt);
|
|
if ah <> nil then
|
|
result := ah.IntCompare(A^, B^, caseInsensitive)
|
|
else if bh <> nil then
|
|
result := - bh.IntCompare(B^, A^, caseInsensitive)
|
|
else
|
|
result := VariantCompAsText(A, B, caseInsensitive); // RawUtf8 convert
|
|
end;
|
|
end;
|
|
|
|
function VariantCompare(const V1, V2: variant): PtrInt;
|
|
begin
|
|
result := FastVarDataComp(@V1, @V2, {caseins=}false);
|
|
end;
|
|
|
|
function VariantCompareI(const V1, V2: variant): PtrInt;
|
|
begin
|
|
result := FastVarDataComp(@V1, @V2, {caseins=}true);
|
|
end;
|
|
|
|
function VariantEquals(const V: Variant; const Str: RawUtf8;
|
|
CaseSensitive: boolean): boolean;
|
|
|
|
function Complex: boolean;
|
|
var
|
|
wasString: boolean;
|
|
tmp: RawUtf8;
|
|
begin
|
|
VariantToUtf8(V, tmp, wasString);
|
|
if CaseSensitive then
|
|
result := (tmp = Str)
|
|
else
|
|
result := PropNameEquals(tmp, Str);
|
|
end;
|
|
|
|
var
|
|
v1, v2: Int64;
|
|
vt: cardinal;
|
|
begin
|
|
vt := TVarData(V).VType;
|
|
with TVarData(V) do
|
|
case vt of
|
|
varEmpty,
|
|
varNull:
|
|
result := Str = '';
|
|
varBoolean:
|
|
result := VBoolean = (Str <> '');
|
|
varString:
|
|
if CaseSensitive then
|
|
result := RawUtf8(VString) = Str
|
|
else
|
|
result := PropNameEquals(RawUtf8(VString), Str);
|
|
else
|
|
if VariantToInt64(V, v1) then
|
|
begin
|
|
SetInt64(pointer(Str), v2);
|
|
result := v1 = v2;
|
|
end
|
|
else
|
|
result := Complex;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ ************** Custom Variant Types with JSON support }
|
|
|
|
var
|
|
SynVariantTypesSafe: TLightLock; // protects only SynRegisterCustomVariantType
|
|
|
|
/// list of custom types (but not DocVariantVType) supporting TryJsonToVariant
|
|
SynVariantTryJsonTypes: array of TSynInvokeableVariantType;
|
|
|
|
function FindSynVariantType(aVarType: cardinal): TSynInvokeableVariantType;
|
|
var
|
|
n: integer;
|
|
t: ^TSynInvokeableVariantType;
|
|
begin
|
|
if (aVarType >= varFirstCustom) and
|
|
(aVarType < varArray) then
|
|
begin
|
|
t := pointer(SynVariantTypes);
|
|
n := PDALen(PAnsiChar(t) - _DALEN)^ + _DAOFF;
|
|
repeat
|
|
result := t^;
|
|
if result.VarType = aVarType then
|
|
exit;
|
|
inc(t);
|
|
dec(n);
|
|
until n = 0;
|
|
end;
|
|
result := nil;
|
|
end;
|
|
|
|
function SynRegisterCustomVariantType(
|
|
aClass: TSynInvokeableVariantTypeClass): TSynInvokeableVariantType;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
SynVariantTypesSafe.Lock;
|
|
try
|
|
for i := 0 to length(SynVariantTypes) - 1 do
|
|
begin
|
|
result := SynVariantTypes[i];
|
|
if PPointer(result)^ = pointer(aClass) then
|
|
// returns already registered instance
|
|
exit;
|
|
end;
|
|
result := aClass.Create; // register variant type
|
|
ObjArrayAdd(SynVariantTypes, result);
|
|
if sioHasTryJsonToVariant in result.Options then
|
|
ObjArrayAdd(SynVariantTryJsonTypes, result);
|
|
finally
|
|
SynVariantTypesSafe.UnLock;
|
|
end;
|
|
end;
|
|
|
|
function SortCompTo(cmp: integer): TVarCompareResult;
|
|
begin
|
|
if cmp = 0 then
|
|
result := crEqual
|
|
else if cmp > 0 then
|
|
result:= crGreaterThan
|
|
else
|
|
result := crLessThan;
|
|
end;
|
|
|
|
|
|
{ TSynInvokeableVariantType }
|
|
|
|
constructor TSynInvokeableVariantType.Create;
|
|
begin
|
|
inherited Create; // call RegisterCustomVariantType(self)
|
|
end;
|
|
|
|
function TSynInvokeableVariantType.IterateCount(const V: TVarData;
|
|
GetObjectAsValues: boolean): integer;
|
|
begin
|
|
result := -1; // this is not an array
|
|
end;
|
|
|
|
procedure TSynInvokeableVariantType.Iterate(var Dest: TVarData;
|
|
const V: TVarData; Index: integer);
|
|
begin
|
|
// do nothing
|
|
end;
|
|
|
|
{$ifdef ISDELPHI}
|
|
function TSynInvokeableVariantType.FixupIdent(const AText: string): string;
|
|
begin
|
|
result := AText; // NO uppercased identifier for our custom types!
|
|
end;
|
|
{$endif ISDELPHI}
|
|
|
|
function TSynInvokeableVariantType.{%H-}IntGet(var Dest: TVarData;
|
|
const Instance: TVarData; Name: PAnsiChar; NameLen: PtrInt;
|
|
NoException: boolean): boolean;
|
|
begin
|
|
raise ESynVariant.CreateUtf8('Unexpected %.IntGet(%): this kind of ' +
|
|
'custom variant does not support fields', [self, Name]);
|
|
end;
|
|
|
|
function TSynInvokeableVariantType.{%H-}IntSet(const Instance, Value: TVarData;
|
|
Name: PAnsiChar; NameLen: PtrInt): boolean;
|
|
begin
|
|
raise ESynVariant.CreateUtf8('Unexpected %.IntSet(%): this kind of ' +
|
|
'custom variant is read-only', [self, Name]);
|
|
end;
|
|
|
|
function TSynInvokeableVariantType.IntCompare(
|
|
const Instance, Another: TVarData; CaseInsensitive: boolean): integer;
|
|
begin
|
|
result := VariantCompAsText(@Instance, @Another, CaseInsensitive);
|
|
end;
|
|
|
|
const
|
|
FROM_VAROP: array[opcmpeq .. opcmpge, TVarCompareResult] of boolean = (
|
|
(false, true, false), // opcmpeq
|
|
(true, false, true), // opcmpne
|
|
(true, false, false), // opcmplt
|
|
(true, true, false), // opcmple
|
|
(false, false, true), // opcmpgt
|
|
(false, true, true)); // opcmpge
|
|
// crLessThan crEqual crGreaterThan
|
|
|
|
function TSynInvokeableVariantType.CompareOp(const Left, Right: TVarData;
|
|
const Operation: TVarOp): boolean;
|
|
var
|
|
vcr: TVarCompareResult;
|
|
begin
|
|
// redirect to Compare() as Delphi RTL does (but not the FPC RTL)
|
|
if not (Operation in [low(FROM_VAROP) .. high(FROM_VAROP)]) then
|
|
raise ESynVariant.CreateUtf8('Unexpected %.CompareOp(%)', [self, ord(Operation)]);
|
|
Compare(Left, Right, vcr);
|
|
result := FROM_VAROP[Operation, vcr];
|
|
end;
|
|
|
|
procedure TSynInvokeableVariantType.Compare(const Left, Right: TVarData;
|
|
var Relationship: TVarCompareResult);
|
|
begin
|
|
Relationship := SortCompTo(IntCompare(Left, Right, {CaseInsen=}false));
|
|
end;
|
|
|
|
const
|
|
DISPATCH_METHOD = 1;
|
|
DISPATCH_PROPERTYGET = 2; // in practice, never generated by the FPC compiler
|
|
DISPATCH_PROPERTYPUT = 4;
|
|
ARGTYPE_MASK = $7f;
|
|
ARGREF_MASK = $80;
|
|
VAR_PARAMNOTFOUND = HRESULT($80020004);
|
|
|
|
{$ifdef FPC}
|
|
var
|
|
DispInvokeArgOrderInverted: boolean; // circumvent FPC 3.2+ breaking change
|
|
{$endif FPC}
|
|
|
|
{$ifdef FPC_VARIANTSETVAR}
|
|
procedure TSynInvokeableVariantType.DispInvoke(
|
|
Dest: PVarData; var Source: TVarData; CallDesc: PCallDesc; Params: Pointer);
|
|
{$else} // see http://mantis.freepascal.org/view.php?id=26773
|
|
{$ifdef ISDELPHIXE7}
|
|
procedure TSynInvokeableVariantType.DispInvoke(
|
|
Dest: PVarData; [ref] const Source: TVarData; // why not just "var" ????
|
|
CallDesc: PCallDesc; Params: Pointer);
|
|
{$else}
|
|
procedure TSynInvokeableVariantType.DispInvoke(
|
|
Dest: PVarData; const Source: TVarData; CallDesc: PCallDesc; Params: Pointer);
|
|
{$endif ISDELPHIXE7}
|
|
{$endif FPC_VARIANTSETVAR}
|
|
var
|
|
name: string;
|
|
res: TVarData;
|
|
namelen, i, asize, n: PtrInt;
|
|
nameptr, a: PAnsiChar;
|
|
v: PVarData;
|
|
args: TVarDataArray; // DoProcedure/DoFunction require a dynamic array
|
|
t: cardinal;
|
|
{$ifdef FPC}
|
|
inverted: boolean;
|
|
{$endif FPC}
|
|
|
|
procedure RaiseInvalid;
|
|
begin
|
|
raise ESynVariant.CreateUtf8('%.DispInvoke: invalid %(%) call',
|
|
[self, name, CallDesc^.ArgCount]);
|
|
end;
|
|
|
|
begin
|
|
// circumvent https://bugs.freepascal.org/view.php?id=38653 and
|
|
// inverted args order FPC bugs, avoid unneeded conversion to varOleString
|
|
// for Delphi, and implement direct IntGet/IntSet calls for all
|
|
n := CallDesc^.ArgCount;
|
|
nameptr := @CallDesc^.ArgTypes[n];
|
|
namelen := StrLen(nameptr);
|
|
// faster direct property getter
|
|
if (Dest <> nil) and
|
|
(n = 0) and
|
|
(CallDesc^.CallType in [DISPATCH_METHOD, DISPATCH_PROPERTYGET]) and
|
|
IntGet(Dest^, Source, nameptr, namelen, {noexception=}false) then
|
|
exit;
|
|
Ansi7ToString(pointer(nameptr), namelen, name);
|
|
if n > 0 then
|
|
begin
|
|
// convert varargs Params buffer into an array of TVarData
|
|
SetLength(args, n);
|
|
{$ifdef FPC} // circumvent FPC 3.2+ inverted order
|
|
inverted := (n > 1) and
|
|
DispInvokeArgOrderInverted;
|
|
if inverted then
|
|
v := @args[n - 1]
|
|
else
|
|
{$endif FPC}
|
|
v := pointer(args);
|
|
a := Params;
|
|
for i := 0 to n - 1 do
|
|
begin
|
|
asize := SizeOf(pointer);
|
|
t := cardinal(CallDesc^.ArgTypes[i]) and ARGTYPE_MASK;
|
|
case t of
|
|
{$ifdef HASVARUSTRARG}
|
|
varUStrArg:
|
|
t := varUString;
|
|
{$endif HASVARUSTRARG}
|
|
varStrArg:
|
|
t := varString;
|
|
end;
|
|
if CallDesc^.ArgTypes[i] and ARGREF_MASK <> 0 then
|
|
begin
|
|
TRttiVarData(v^).VType := t or varByRef;
|
|
v^.VPointer := PPointer(a)^;
|
|
end
|
|
else
|
|
begin
|
|
TRttiVarData(v^).VType := t;
|
|
case t of
|
|
varError:
|
|
begin
|
|
v^.VError := VAR_PARAMNOTFOUND;
|
|
asize := 0;
|
|
end;
|
|
varVariant:
|
|
{$ifdef CPU32DELPHI}
|
|
begin
|
|
v^ := PVarData(a)^;
|
|
asize := SizeOf(TVarData); // pushed by value
|
|
end;
|
|
{$else}
|
|
v^ := PPVarData(a)^^; // pushed by reference (as other parameters)
|
|
{$endif CPU32DELPHI}
|
|
varDouble,
|
|
varCurrency,
|
|
varDate,
|
|
varInt64,
|
|
varWord64:
|
|
begin
|
|
v^.VInt64 := PInt64(a)^;
|
|
asize := SizeOf(Int64);
|
|
end;
|
|
// small values are stored as pointers on stack but pushed as 32-bit
|
|
varSingle,
|
|
varSmallint,
|
|
varInteger,
|
|
varLongWord,
|
|
varBoolean,
|
|
varShortInt,
|
|
varByte,
|
|
varWord:
|
|
v^.VInteger := PInteger(a)^; // we assume little endian
|
|
else
|
|
v^.VAny := PPointer(a)^; // e.g. varString or varOleStr
|
|
end;
|
|
end;
|
|
inc(a, asize);
|
|
{$ifdef FPC}
|
|
if inverted then
|
|
dec(v)
|
|
else
|
|
{$endif FPC}
|
|
inc(v);
|
|
end;
|
|
end;
|
|
case CallDesc^.CallType of
|
|
// note: IntGet was already tried in function trailer
|
|
DISPATCH_METHOD:
|
|
if Dest <> nil then
|
|
begin
|
|
if not DoFunction(Dest^, Source, name, args) then
|
|
RaiseInvalid;
|
|
end
|
|
else if not DoProcedure(Source, name, args) then
|
|
begin
|
|
PCardinal(@res)^ := varEmpty;
|
|
try
|
|
if not DoFunction(Dest^, Source, name, args) then
|
|
RaiseInvalid;
|
|
finally
|
|
VarClearProc(res);
|
|
end;
|
|
end;
|
|
DISPATCH_PROPERTYGET:
|
|
if (Dest = nil) or
|
|
not DoFunction(Dest^, Source, name, args) then
|
|
RaiseInvalid;
|
|
DISPATCH_PROPERTYPUT:
|
|
if (Dest <> nil) or
|
|
(n <> 1) or
|
|
not IntSet(Source, args[0], nameptr, namelen) then
|
|
RaiseInvalid;
|
|
else
|
|
RaiseInvalid;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynInvokeableVariantType.Clear(var V: TVarData);
|
|
begin
|
|
ZeroFill(@V); // will set V.VType := varEmpty
|
|
end;
|
|
|
|
procedure TSynInvokeableVariantType.Copy(var Dest: TVarData;
|
|
const Source: TVarData; const Indirect: boolean);
|
|
begin
|
|
if Indirect then
|
|
SetVariantByRef(variant(Source), variant(Dest))
|
|
else
|
|
begin
|
|
VarClear(variant(Dest)); // Dest may be a complex type
|
|
Dest := Source;
|
|
end;
|
|
end;
|
|
|
|
procedure TSynInvokeableVariantType.CopyByValue(
|
|
var Dest: TVarData; const Source: TVarData);
|
|
begin
|
|
Copy(Dest, Source, {Indirect=} false);
|
|
end;
|
|
|
|
function TSynInvokeableVariantType.TryJsonToVariant(var Json: PUtf8Char;
|
|
var Value: variant; EndOfObject: PUtf8Char): boolean;
|
|
begin
|
|
result := false;
|
|
end;
|
|
|
|
procedure TSynInvokeableVariantType.ToJson(W: TJsonWriter; Value: PVarData);
|
|
begin
|
|
raise ESynVariant.CreateUtf8('%.ToJson is not implemented', [self]);
|
|
end;
|
|
|
|
procedure TSynInvokeableVariantType.ToJson(Value: PVarData;
|
|
var Json: RawUtf8; const Prefix, Suffix: RawUtf8; Format: TTextWriterJsonFormat);
|
|
var
|
|
W: TJsonWriter;
|
|
temp: TTextWriterStackBuffer;
|
|
begin
|
|
W := TJsonWriter.CreateOwnedStream(temp);
|
|
try
|
|
if Prefix <> '' then
|
|
W.AddString(Prefix);
|
|
ToJson(W, Value); // direct TSynInvokeableVariantType serialization
|
|
if Suffix <> '' then
|
|
W.AddString(Suffix);
|
|
W.SetText(Json, Format);
|
|
finally
|
|
W.Free;
|
|
end;
|
|
end;
|
|
|
|
function TSynInvokeableVariantType.IsOfType(const V: variant): boolean;
|
|
var
|
|
vt: cardinal;
|
|
vd: PVarData;
|
|
{%H-}begin
|
|
if self <> nil then
|
|
begin
|
|
vd := @V;
|
|
repeat
|
|
vt := vd^.VType;
|
|
if vt <> varVariantByRef then
|
|
break;
|
|
vd := vd^.VPointer;
|
|
until false;
|
|
result := vt = VarType;
|
|
end
|
|
else
|
|
result := false;
|
|
end;
|
|
|
|
function TSynInvokeableVariantType.IsVoid(const V: TVarData): boolean;
|
|
begin
|
|
result := false; // not void by default
|
|
end;
|
|
|
|
function TSynInvokeableVariantType.FindSynVariantType(aVarType: cardinal;
|
|
out CustomType: TSynInvokeableVariantType): boolean;
|
|
var
|
|
ct: TSynInvokeableVariantType;
|
|
begin
|
|
if (self <> nil) and
|
|
(aVarType = VarType) then
|
|
ct := self
|
|
else
|
|
ct := mormot.core.variants.FindSynVariantType(aVarType);
|
|
CustomType := ct;
|
|
result := ct <> nil;
|
|
end;
|
|
|
|
function TSynInvokeableVariantType.FindSynVariantType(
|
|
aVarType: cardinal): TSynInvokeableVariantType;
|
|
begin
|
|
if aVarType = VarType then
|
|
result := self
|
|
else
|
|
result := mormot.core.variants.FindSynVariantType(aVarType);
|
|
end;
|
|
|
|
procedure TSynInvokeableVariantType.Lookup(var Dest: TVarData;
|
|
const Instance: TVarData; FullName: PUtf8Char; PathDelim: AnsiChar);
|
|
var
|
|
handler: TSynInvokeableVariantType;
|
|
v, tmp: TVarData; // PVarData wouldn't store e.g. RowID/count
|
|
vt: cardinal;
|
|
n: ShortString;
|
|
begin
|
|
TRttiVarData(Dest).VType := varEmpty; // left to Unassigned if not found
|
|
v := Instance;
|
|
repeat
|
|
vt := v.VType;
|
|
if vt <> varVariantByRef then
|
|
break;
|
|
v := PVarData(v.VPointer)^;
|
|
until false;
|
|
repeat
|
|
if vt < varFirstCustom then
|
|
exit; // we need a complex type to lookup
|
|
GetNextItemShortString(FullName, @n, PathDelim); // n will end with #0
|
|
if n[0] = #0 then
|
|
exit;
|
|
handler := self;
|
|
if vt <> VarType then
|
|
begin
|
|
handler := mormot.core.variants.FindSynVariantType(vt);
|
|
if handler = nil then
|
|
exit;
|
|
end;
|
|
tmp := v; // v will be modified in-place
|
|
TRttiVarData(v).VType := varEmpty; // IntGet() would clear it otherwise!
|
|
if not handler.IntGet(v, tmp, @n[1], ord(n[0]), {noexc=}true) then
|
|
exit; // property not found (no exception should be raised in Lookup)
|
|
repeat
|
|
vt := v.VType;
|
|
if vt <> varVariantByRef then
|
|
break;
|
|
v := PVarData(v.VPointer)^;
|
|
until false;
|
|
if (vt = DocVariantVType) and
|
|
(TDocVariantData(v).VCount = 0) then
|
|
// recognize void TDocVariant as null
|
|
v.VType := varNull; // do not use PCardinal/TRttiVarData(v).VType here
|
|
until FullName = nil;
|
|
Dest := v;
|
|
end;
|
|
|
|
function CustomVariantToJson(W: TJsonWriter; Value: PVarData;
|
|
Escape: TTextWriterKind): boolean;
|
|
var
|
|
v: TCustomVariantType;
|
|
tmp: variant;
|
|
begin
|
|
result := true;
|
|
if FindCustomVariantType(Value.VType, v) then
|
|
if v.InheritsFrom(TSynInvokeableVariantType) then
|
|
TSynInvokeableVariantType(v).ToJson(W, Value)
|
|
else
|
|
try
|
|
v.CastTo(TVarData(tmp), Value^, varNativeString);
|
|
W.AddVariant(tmp, Escape);
|
|
except
|
|
result := false;
|
|
end
|
|
else
|
|
result := false;
|
|
end;
|
|
|
|
|
|
function ToText(kind: TDocVariantKind): PShortString;
|
|
begin
|
|
result := GetEnumName(TypeInfo(TDocVariantKind), ord(kind));
|
|
end;
|
|
|
|
procedure __VariantSaveJsonEscape(const Value: variant; var Json: RawUtf8;
|
|
Escape: TTextWriterKind);
|
|
var
|
|
temp: TTextWriterStackBuffer;
|
|
begin
|
|
with TJsonWriter.CreateOwnedStream(temp) do
|
|
try
|
|
AddVariant(Value, Escape); // will use mormot.core.json serialization
|
|
SetText(Json, jsonCompact);
|
|
finally
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
procedure __VariantSaveJson(V: PVarData; Escape: TTextWriterKind;
|
|
var result: RawUtf8);
|
|
var
|
|
cv: TSynInvokeableVariantType;
|
|
vt: cardinal;
|
|
dummy: boolean;
|
|
begin
|
|
// is likely to be called from AddVariant() but can be used for simple values
|
|
if cardinal(V.VType) = varVariantByRef then
|
|
V := V^.VPointer;
|
|
cv := FindSynVariantType(V.VType);
|
|
if cv = nil then
|
|
begin
|
|
vt := V.VType;
|
|
if (vt >= varFirstCustom) or
|
|
((Escape <> twNone) and
|
|
not (vt in [varEmpty..varDate, varBoolean, varShortInt..varWord64])) then
|
|
__VariantSaveJsonEscape(PVariant(V)^, result, Escape)
|
|
else
|
|
VariantToUtf8(PVariant(V)^, result, dummy); // no escape for simple values
|
|
end
|
|
else
|
|
cv.ToJson(V, result);
|
|
end;
|
|
|
|
|
|
{ EDocVariant }
|
|
|
|
class procedure EDocVariant.RaiseSafe(Kind: TDocVariantKind);
|
|
begin
|
|
raise CreateUtf8('_Safe(%)?', [ToText(Kind)^]);
|
|
end;
|
|
|
|
{ TDocVariant }
|
|
|
|
destructor TDocVariant.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
fInternNames.Free;
|
|
fInternValues.Free;
|
|
end;
|
|
|
|
const
|
|
_GETMETHOD: array[0..3] of PAnsiChar = (
|
|
'COUNT', // 0
|
|
'KIND', // 1
|
|
'JSON', // 2
|
|
nil);
|
|
|
|
function IntGetPseudoProp(ndx: PtrInt; const source: TDocVariantData;
|
|
var Dest: variant): boolean;
|
|
begin
|
|
// sub-function to avoid temporary RawUtf8 for source.ToJson
|
|
result := true;
|
|
case ndx of
|
|
0:
|
|
Dest := source.Count;
|
|
1:
|
|
Dest := ord(source.GetKind);
|
|
2:
|
|
RawUtf8ToVariant(source.ToJson, Dest);
|
|
else
|
|
result := false;
|
|
end;
|
|
end;
|
|
|
|
function TDocVariant.IntGet(var Dest: TVarData; const Instance: TVarData;
|
|
Name: PAnsiChar; NameLen: PtrInt; NoException: boolean): boolean;
|
|
var
|
|
dv: TDocVariantData absolute Instance;
|
|
ndx: integer;
|
|
begin
|
|
if Name = nil then
|
|
result := false
|
|
else if (NameLen > 4) and
|
|
(Name[0] = '_') and
|
|
IntGetPseudoProp(IdemPPChar(@Name[1], @_GETMETHOD), dv, variant(Dest)) then
|
|
result := true
|
|
else
|
|
begin
|
|
ndx := dv.GetValueIndex(pointer(Name), NameLen, dv.IsCaseSensitive);
|
|
if ndx < 0 then
|
|
if NoException or
|
|
dv.Has(dvoReturnNullForUnknownProperty) then
|
|
begin
|
|
SetVariantNull(PVariant(@Dest)^);
|
|
result := false;
|
|
end
|
|
else
|
|
raise EDocVariant.CreateUtf8('[%] property not found', [Name])
|
|
else
|
|
begin
|
|
SetVariantByRef(dv.VValue[ndx], PVariant(@Dest)^);
|
|
result := true;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TDocVariant.IntSet(const Instance, Value: TVarData;
|
|
Name: PAnsiChar; NameLen: PtrInt): boolean;
|
|
var
|
|
ndx: PtrInt;
|
|
dv: TDocVariantData absolute Instance;
|
|
begin
|
|
result := true;
|
|
if dv.IsArray and
|
|
(PWord(Name)^ = ord('_')) then
|
|
begin
|
|
dv.AddItem(variant(Value));
|
|
exit;
|
|
end;
|
|
ndx := dv.GetValueIndex(pointer(Name), NameLen, dv.IsCaseSensitive);
|
|
if ndx < 0 then
|
|
ndx := dv.InternalAddBuf(pointer(Name), NameLen);
|
|
dv.InternalSetValue(ndx, variant(Value));
|
|
end;
|
|
|
|
function TDocVariant.IntCompare(const Instance, Another: TVarData;
|
|
CaseInsensitive: boolean): integer;
|
|
var
|
|
l, r: PDocVariantData;
|
|
begin
|
|
if _Safe(variant(Instance), l) and // is likely to be a TDocVariant
|
|
_Safe(variant(Another), r) then
|
|
result := l^.Compare(r^, CaseInsensitive)
|
|
else // inlined inherited
|
|
result := VariantCompAsText(@Instance, @Another, CaseInsensitive);
|
|
end;
|
|
|
|
function TDocVariant.IterateCount(const V: TVarData;
|
|
GetObjectAsValues: boolean): integer;
|
|
var
|
|
Data: TDocVariantData absolute V;
|
|
begin
|
|
if Data.IsArray or
|
|
(GetObjectAsValues and
|
|
Data.IsObject) then
|
|
result := Data.VCount
|
|
else
|
|
result := -1;
|
|
end;
|
|
|
|
procedure TDocVariant.Iterate(var Dest: TVarData;
|
|
const V: TVarData; Index: integer);
|
|
var
|
|
Data: TDocVariantData absolute V;
|
|
begin // note: IterateCount() may accept IsObject values[]
|
|
if cardinal(Index) < cardinal(Data.VCount) then
|
|
Dest := TVarData(Data.VValue[Index])
|
|
else
|
|
TRttiVarData(Dest).VType := varEmpty;
|
|
end;
|
|
|
|
function TDocVariant.IsVoid(const V: TVarData): boolean;
|
|
begin
|
|
result := TDocVariantData(V).Count > 0;
|
|
end;
|
|
|
|
function TDocVariant.DoProcedure(const V: TVarData; const Name: string;
|
|
const Arguments: TVarDataArray): boolean;
|
|
var
|
|
Data: PDocVariantData;
|
|
begin
|
|
result := false;
|
|
Data := @V; // allow to modify a const argument
|
|
case length(Arguments) of
|
|
0:
|
|
if SameText(Name, 'Clear') then
|
|
begin
|
|
Data^.Reset;
|
|
result := true;
|
|
end;
|
|
1:
|
|
if SameText(Name, 'Add') then
|
|
begin
|
|
Data^.AddItem(variant(Arguments[0]));
|
|
result := true;
|
|
end
|
|
else if SameText(Name, 'Delete') then
|
|
begin
|
|
Data^.Delete(Data^.GetValueIndex(ToUtf8(Arguments[0])));
|
|
result := true;
|
|
end;
|
|
2:
|
|
if SameText(Name, 'Add') then
|
|
begin
|
|
Data^.AddValue(ToUtf8(Arguments[0]), variant(Arguments[1]));
|
|
result := true;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TDocVariant.DoFunction(var Dest: TVarData; const V: TVarData;
|
|
const Name: string; const Arguments: TVarDataArray): boolean;
|
|
var
|
|
ndx: integer;
|
|
Data: PDocVariantData;
|
|
temp: RawUtf8;
|
|
begin
|
|
result := true;
|
|
Data := @V; // allow to modify a const argument
|
|
case length(Arguments) of
|
|
1:
|
|
if SameText(Name, 'Exists') then
|
|
begin
|
|
variant(Dest) := Data.GetValueIndex(ToUtf8(Arguments[0])) >= 0;
|
|
exit;
|
|
end
|
|
else if SameText(Name, 'NameIndex') then
|
|
begin
|
|
variant(Dest) := Data.GetValueIndex(ToUtf8(Arguments[0]));
|
|
exit;
|
|
end
|
|
else if VariantToInteger(variant(Arguments[0]), ndx) then
|
|
begin
|
|
if (Name = '_') or
|
|
SameText(Name, 'Value') then
|
|
begin
|
|
Data.RetrieveValueOrRaiseException(ndx, variant(Dest), true);
|
|
exit;
|
|
end
|
|
else if SameText(Name, 'Name') then
|
|
begin
|
|
Data.RetrieveNameOrRaiseException(ndx, temp);
|
|
RawUtf8ToVariant(temp, variant(Dest));
|
|
exit;
|
|
end;
|
|
end
|
|
else if (Name = '_') or
|
|
SameText(Name, 'Value') then
|
|
begin
|
|
temp := ToUtf8(Arguments[0]);
|
|
Data.RetrieveValueOrRaiseException(pointer(temp), length(temp),
|
|
Data.IsCaseSensitive, variant(Dest), true);
|
|
exit;
|
|
end;
|
|
end;
|
|
result := Data.Has(dvoReturnNullForUnknownProperty); // to avoid error
|
|
end;
|
|
|
|
procedure TDocVariant.ToJson(W: TJsonWriter; Value: PVarData);
|
|
var
|
|
forced: TTextWriterOptions;
|
|
nam: PPUtf8Char;
|
|
val: PVariant;
|
|
vt: cardinal;
|
|
n: integer;
|
|
checkExtendedPropName: boolean;
|
|
begin
|
|
repeat
|
|
vt := Value^.VType;
|
|
if vt <> varVariantByRef then
|
|
break;
|
|
Value := Value^.VPointer;
|
|
until false;
|
|
if vt <> DocVariantVType then
|
|
begin
|
|
W.AddNull;
|
|
exit;
|
|
end;
|
|
forced := [];
|
|
if [twoForceJsonExtended, twoForceJsonStandard] * W.CustomOptions = [] then
|
|
begin
|
|
if PDocVariantData(Value)^.Has(dvoSerializeAsExtendedJson) then
|
|
forced := [twoForceJsonExtended]
|
|
else
|
|
forced := [twoForceJsonStandard];
|
|
W.CustomOptions := W.CustomOptions + forced;
|
|
end;
|
|
n := PDocVariantData(Value)^.VCount;
|
|
val := pointer(PDocVariantData(Value)^.VValue);
|
|
if PDocVariantData(Value)^.IsObject then
|
|
begin
|
|
checkExtendedPropName := twoForceJsonExtended in W.CustomOptions;
|
|
W.Add('{');
|
|
nam := pointer(PDocVariantData(Value)^.VName);
|
|
if n <> 0 then
|
|
repeat
|
|
if checkExtendedPropName and
|
|
JsonPropNameValid(nam^) then
|
|
W.AddShort(nam^, PStrLen(nam^ - _STRLEN)^)
|
|
else
|
|
begin
|
|
W.AddDirect('"');
|
|
W.AddJsonEscape(nam^);
|
|
W.AddDirect('"');
|
|
end;
|
|
W.AddDirect(':');
|
|
W.AddVariant(val^, twJsonEscape);
|
|
dec(n);
|
|
if n = 0 then
|
|
break;
|
|
W.AddComma;
|
|
inc(nam);
|
|
inc(val);
|
|
until false;
|
|
W.AddDirect('}');
|
|
end
|
|
else if PDocVariantData(Value)^.IsArray then
|
|
begin
|
|
W.Add('[');
|
|
if n <> 0 then
|
|
repeat
|
|
W.AddVariant(val^, twJsonEscape);
|
|
dec(n);
|
|
if n = 0 then
|
|
break;
|
|
W.AddComma;
|
|
inc(val);
|
|
until false;
|
|
W.AddDirect(']');
|
|
end
|
|
else
|
|
W.AddNull;
|
|
if forced <> [] then
|
|
W.CustomOptions := W.CustomOptions - forced;
|
|
end;
|
|
|
|
procedure TDocVariant.Clear(var V: TVarData);
|
|
begin
|
|
//Assert(V.VType=DocVariantVType);
|
|
TDocVariantData(V).ClearFast;
|
|
end;
|
|
|
|
procedure TDocVariant.Copy(var Dest: TVarData; const Source: TVarData;
|
|
const Indirect: boolean);
|
|
begin
|
|
//Assert(Source.VType=DocVariantVType);
|
|
if Indirect then
|
|
SetVariantByRef(variant(Source), variant(Dest))
|
|
else
|
|
CopyByValue(Dest, Source);
|
|
end;
|
|
|
|
procedure TDocVariant.CopyByValue(var Dest: TVarData; const Source: TVarData);
|
|
var
|
|
S: TDocVariantData absolute Source;
|
|
D: TDocVariantData absolute Dest;
|
|
begin
|
|
//Assert(Source.VType=DocVariantVType);
|
|
VarClearAndSetType(variant(Dest), PCardinal(@S)^); // VType + VOptions
|
|
pointer(D.VName) := nil; // avoid GPF
|
|
pointer(D.VValue) := nil;
|
|
D.VCount := S.VCount;
|
|
if S.VCount = 0 then
|
|
exit; // no data to copy
|
|
D.VName := S.VName;
|
|
if S.Has(dvoValueCopiedByReference) then
|
|
D.VValue := S.VValue // byref copy of the whole array
|
|
else
|
|
D.VValue := system.copy(S.VValue); // new array, but byref values
|
|
end;
|
|
|
|
procedure TDocVariant.Cast(var Dest: TVarData; const Source: TVarData);
|
|
begin
|
|
CastTo(Dest, Source, VarType);
|
|
end;
|
|
|
|
procedure TDocVariant.CastTo(var Dest: TVarData; const Source: TVarData;
|
|
const AVarType: TVarType);
|
|
var
|
|
json: RawUtf8;
|
|
wasString: boolean;
|
|
begin
|
|
if AVarType = VarType then
|
|
begin
|
|
VariantToUtf8(Variant(Source), json, wasString);
|
|
if wasString then
|
|
begin
|
|
VarClear(variant(Dest));
|
|
variant(Dest) := _JsonFast(json); // convert from JSON text
|
|
exit;
|
|
end;
|
|
RaiseCastError;
|
|
end
|
|
else
|
|
begin
|
|
if Source.VType <> VarType then
|
|
RaiseCastError;
|
|
DocVariantType.ToJson(@Source, json);
|
|
RawUtf8ToVariant(json, Dest, AVarType); // convert to JSON text
|
|
end;
|
|
end;
|
|
|
|
class procedure TDocVariant.New(out aValue: variant;
|
|
aOptions: TDocVariantOptions);
|
|
begin
|
|
TDocVariantData(aValue).Init(aOptions);
|
|
end;
|
|
|
|
class procedure TDocVariant.NewFast(out aValue: variant;
|
|
aKind: TDocVariantKind);
|
|
begin
|
|
TVarData(aValue) := DV_FAST[aKind];
|
|
end;
|
|
|
|
class procedure TDocVariant.IsOfTypeOrNewFast(var aValue: variant);
|
|
begin
|
|
if DocVariantType.IsOfType(aValue) then
|
|
exit;
|
|
VarClear(aValue);
|
|
TVarData(aValue) := DV_FAST[dvUndefined];
|
|
end;
|
|
|
|
class procedure TDocVariant.NewFast(const aValues: array of PDocVariantData;
|
|
aKind: TDocVariantKind);
|
|
var
|
|
i: PtrInt;
|
|
def: PDocVariantData;
|
|
begin
|
|
def := @DV_FAST[aKind];
|
|
for i := 0 to high(aValues) do
|
|
aValues[i]^ := def^;
|
|
end;
|
|
|
|
class function TDocVariant.New(Options: TDocVariantOptions): Variant;
|
|
begin
|
|
VarClear(result{%H-});
|
|
TDocVariantData(result).Init(Options);
|
|
end;
|
|
|
|
class function TDocVariant.NewObject(const NameValuePairs: array of const;
|
|
Options: TDocVariantOptions): variant;
|
|
begin
|
|
VarClear(result{%H-});
|
|
TDocVariantData(result).InitObject(NameValuePairs, Options);
|
|
end;
|
|
|
|
class function TDocVariant.NewArray(const Items: array of const;
|
|
Options: TDocVariantOptions): variant;
|
|
begin
|
|
VarClear(result{%H-});
|
|
TDocVariantData(result).InitArray(Items, Options);
|
|
end;
|
|
|
|
class function TDocVariant.NewArray(const Items: TVariantDynArray;
|
|
Options: TDocVariantOptions): variant;
|
|
begin
|
|
VarClear(result{%H-});
|
|
TDocVariantData(result).InitArrayFromVariants(Items, Options);
|
|
end;
|
|
|
|
class function TDocVariant.NewJson(const Json: RawUtf8;
|
|
Options: TDocVariantOptions): variant;
|
|
begin
|
|
_Json(Json, result, Options);
|
|
end;
|
|
|
|
class function TDocVariant.NewUnique(const SourceDocVariant: variant;
|
|
Options: TDocVariantOptions): variant;
|
|
begin
|
|
VarClear(result{%H-});
|
|
TDocVariantData(result).InitCopy(SourceDocVariant, Options);
|
|
end;
|
|
|
|
class procedure TDocVariant.GetSingleOrDefault(
|
|
const docVariantArray, default: variant; var result: variant);
|
|
var
|
|
vt: cardinal;
|
|
begin
|
|
vt := TVarData(docVariantArray).VType;
|
|
if vt = varVariantByRef then
|
|
GetSingleOrDefault(
|
|
PVariant(TVarData(docVariantArray).VPointer)^, default, result)
|
|
else if (vt <> DocVariantVType) or
|
|
(TDocVariantData(docVariantArray).Count <> 1) or
|
|
not TDocVariantData(docVariantArray).IsArray then
|
|
result := default
|
|
else
|
|
result := TDocVariantData(docVariantArray).Values[0];
|
|
end;
|
|
|
|
function DocVariantData(const DocVariant: variant): PDocVariantData;
|
|
var
|
|
docv, vt: cardinal;
|
|
begin
|
|
result := @DocVariant;
|
|
docv := DocVariantVType;
|
|
vt := result^.VType;
|
|
if vt = docv then
|
|
exit
|
|
else if vt = varVariantByRef then
|
|
begin
|
|
result := PVarData(result)^.VPointer;
|
|
if cardinal(result^.VType) = docv then
|
|
exit;
|
|
end;
|
|
raise EDocVariant.CreateUtf8('Unexpected DocVariantData(var%)',
|
|
[VariantTypeName(PVarData(result))^]);
|
|
end;
|
|
|
|
{$ifdef FPC_OR_UNICODE} // Delphi has problems inlining this :(
|
|
function _Safe(const DocVariant: variant): PDocVariantData;
|
|
var
|
|
docv, vt: cardinal;
|
|
begin
|
|
result := @DocVariant;
|
|
docv := DocVariantVType;
|
|
vt := result^.VType;
|
|
if vt = docv then
|
|
exit
|
|
else if vt = varVariantByRef then
|
|
begin
|
|
result := PVarData(result)^.VPointer;
|
|
if cardinal(result^.VType) = docv then
|
|
exit;
|
|
end;
|
|
result := @DocVariantDataFake;
|
|
end;
|
|
{$else} // fallback for Delphi 7/2007
|
|
function _Safe(const DocVariant: variant): PDocVariantData;
|
|
asm
|
|
mov ecx, DocVariantVType
|
|
movzx edx, word ptr [eax].TVarData.VType
|
|
cmp edx, ecx
|
|
jne @by
|
|
ret
|
|
@ptr: mov eax, [eax].TVarData.VPointer
|
|
movzx edx, word ptr [eax].TVarData.VType
|
|
cmp edx, ecx
|
|
je @ok
|
|
@by: cmp edx, varVariantByRef
|
|
je @ptr
|
|
lea eax, [DocVariantDataFake]
|
|
@ok:
|
|
end;
|
|
{$endif FPC_OR_UNICODE}
|
|
|
|
function _Safe(const DocVariant: variant; out DV: PDocVariantData): boolean;
|
|
var
|
|
docv, vt: cardinal;
|
|
v: PDocVariantData;
|
|
{$ifdef FPC} // latest Delphi compilers have problems inlining labels
|
|
label
|
|
no;
|
|
{$endif FPC}
|
|
begin
|
|
docv := DocVariantVType;
|
|
v := @DocVariant;
|
|
vt := v^.VType;
|
|
{$ifdef ISDELPHI}
|
|
result := false;
|
|
{$endif ISDELPHI}
|
|
if vt <> docv then
|
|
if vt <> varVariantByRef then
|
|
begin
|
|
{$ifdef FPC}
|
|
no: result := false;
|
|
{$endif FPC}
|
|
exit;
|
|
end
|
|
else
|
|
begin
|
|
v := PVarData(v)^.VPointer;
|
|
if cardinal(v^.VType) <> docv then
|
|
{$ifdef FPC}
|
|
goto no;
|
|
{$else}
|
|
exit;
|
|
{$endif FPC}
|
|
end;
|
|
DV := v;
|
|
result := true;
|
|
end;
|
|
|
|
function _SafeArray(const Value: variant; out DV: PDocVariantData): boolean;
|
|
begin
|
|
result := _Safe(Value, DV) and
|
|
not {%H-}DV^.IsObject;
|
|
end;
|
|
|
|
function _SafeArray(const Value: variant; ExpectedCount: integer;
|
|
out DV: PDocVariantData): boolean;
|
|
begin
|
|
result := _Safe(Value, DV) and
|
|
{%H-}DV^.IsArray and
|
|
(DV^.Count = ExpectedCount);
|
|
end;
|
|
|
|
function _SafeObject(const Value: variant; out DV: PDocVariantData): boolean;
|
|
begin
|
|
result := _Safe(Value, DV) and
|
|
not {%H-}DV^.IsArray;
|
|
end;
|
|
|
|
function _Safe(const DocVariant: variant;
|
|
ExpectedKind: TDocVariantKind): PDocVariantData;
|
|
begin
|
|
if ExpectedKind = dvArray then
|
|
begin
|
|
if _SafeArray(DocVariant, result) then
|
|
exit;
|
|
end
|
|
else if (ExpectedKind = dvObject) and
|
|
_SafeObject(DocVariant, result) then
|
|
exit;
|
|
EDocVariant.RaiseSafe(ExpectedKind);
|
|
end;
|
|
|
|
function _DV(const DocVariant: variant): TDocVariantData;
|
|
begin
|
|
result := _Safe(DocVariant)^;
|
|
end;
|
|
|
|
function _DV(const DocVariant: variant;
|
|
ExpectedKind: TDocVariantKind): TDocVariantData;
|
|
begin
|
|
result := _Safe(DocVariant, ExpectedKind)^;
|
|
end;
|
|
|
|
function _DV(const DocVariant: variant; var DV: TDocVariantData): boolean;
|
|
var
|
|
docv, vt: cardinal;
|
|
v: PDocVariantData;
|
|
label
|
|
no;
|
|
begin
|
|
docv := DocVariantVType;
|
|
v := @DocVariant;
|
|
vt := v^.VType;
|
|
if vt <> docv then
|
|
if vt <> varVariantByRef then
|
|
begin
|
|
no: result := false;
|
|
exit;
|
|
end
|
|
else
|
|
begin
|
|
v := PVarData(v)^.VPointer;
|
|
if cardinal(v^.VType) <> docv then
|
|
goto no;
|
|
end;
|
|
DV := v^;
|
|
result := true;
|
|
end;
|
|
|
|
function _Csv(const DocVariantOrString: variant): RawUtf8;
|
|
begin
|
|
with _Safe(DocVariantOrString)^ do
|
|
if IsArray then
|
|
result := ToCsv
|
|
else if IsObject or
|
|
not VariantToText(DocVariantOrString, result) then
|
|
result := '';
|
|
end;
|
|
|
|
function ObjectToVariant(Value: TObject; EnumSetsAsText: boolean): variant;
|
|
const
|
|
OPTIONS: array[boolean] of TTextWriterWriteObjectOptions = (
|
|
[woDontStoreDefault], [woDontStoreDefault, woEnumSetsAsText]);
|
|
begin
|
|
ObjectToVariant(Value, result, OPTIONS[EnumSetsAsText]);
|
|
end;
|
|
|
|
function ObjectToVariantDebug(Value: TObject;
|
|
const ContextFormat: RawUtf8; const ContextArgs: array of const;
|
|
const ContextName: RawUtf8): variant;
|
|
begin
|
|
ObjectToVariant(Value, result, [woDontStoreDefault, woEnumSetsAsText]);
|
|
if ContextFormat <> '' then
|
|
if ContextFormat[1] = '{' then
|
|
_ObjAddProps([ContextName,
|
|
_JsonFastFmt(ContextFormat, [], ContextArgs)], result)
|
|
else
|
|
_ObjAddProps([ContextName,
|
|
FormatUtf8(ContextFormat, ContextArgs)], result);
|
|
end;
|
|
|
|
procedure ObjectToVariant(Value: TObject; var result: variant;
|
|
Options: TTextWriterWriteObjectOptions);
|
|
var
|
|
json: RawUtf8;
|
|
begin
|
|
VarClear(result{%H-});
|
|
json := ObjectToJson(Value, Options);
|
|
if PDocVariantData(@result)^.InitJsonInPlace(
|
|
pointer(json), JSON_FAST) = nil then
|
|
VarClear(result);
|
|
end;
|
|
|
|
function SetNameToVariant(Value: cardinal; Info: TRttiCustom;
|
|
FullSetsAsStar: boolean): variant;
|
|
var
|
|
bit: PtrInt;
|
|
PS: PShortString;
|
|
arr: TDocVariantData;
|
|
begin
|
|
TVarData(arr) := DV_FAST[dvArray];
|
|
if FullSetsAsStar and
|
|
GetAllBits(Value, Info.Cache.EnumMax + 1) then
|
|
arr.AddItem('*')
|
|
else
|
|
with Info.Cache do
|
|
begin
|
|
PS := EnumList;
|
|
for bit := EnumMin to EnumMax do
|
|
begin
|
|
if GetBitPtr(@Value, bit) then
|
|
arr.AddItem(PS^);
|
|
inc(PByte(PS), ord(PS^[0]) + 1); // next item
|
|
end;
|
|
end;
|
|
result := variant(arr);
|
|
end;
|
|
|
|
function SetNameToVariant(Value: cardinal; Info: PRttiInfo;
|
|
FullSetsAsStar: boolean): variant;
|
|
begin
|
|
result := SetNameToVariant(Value, Rtti.RegisterType(Info), FullSetsAsStar);
|
|
end;
|
|
|
|
function DocVariantToObject(var doc: TDocVariantData; obj: TObject;
|
|
objRtti: TRttiCustom): boolean;
|
|
var
|
|
p: PtrInt;
|
|
prop: PRttiCustomProp;
|
|
begin
|
|
if doc.IsObject and
|
|
(doc.Count > 0) and
|
|
(obj <> nil) then
|
|
begin
|
|
if objRtti = nil then
|
|
objRtti := Rtti.RegisterClass(PClass(obj)^);
|
|
for p := 0 to doc.Count - 1 do
|
|
begin
|
|
prop := objRtti.Props.Find(doc.Names[p]);
|
|
if prop <> nil then
|
|
prop^.Prop.SetValue(obj, doc.Values[p]);
|
|
end;
|
|
result := true;
|
|
end
|
|
else
|
|
result := false;
|
|
end;
|
|
|
|
procedure DocVariantToObjArray(var arr: TDocVariantData; var objArray;
|
|
objClass: TClass);
|
|
var
|
|
info: TRttiCustom;
|
|
i: PtrInt;
|
|
obj: TObjectDynArray absolute objArray;
|
|
begin
|
|
if objClass = nil then
|
|
exit;
|
|
ObjArrayClear(obj);
|
|
if (not arr.IsArray) or
|
|
(arr.Count = 0) then
|
|
exit;
|
|
info := Rtti.RegisterClass(objClass);
|
|
SetLength(obj, arr.Count);
|
|
for i := 0 to arr.Count - 1 do
|
|
begin
|
|
obj[i] := info.ClassNewInstance;
|
|
DocVariantToObject(_Safe(arr.Values[i])^, obj[i], info);
|
|
end;
|
|
end;
|
|
|
|
function ObjectDefaultToVariant(aClass: TClass;
|
|
aOptions: TDocVariantOptions): variant;
|
|
var
|
|
tempvoid: TObject;
|
|
json: RawUtf8;
|
|
begin
|
|
VarClear(result);
|
|
tempvoid := Rtti.RegisterClass(aClass).ClassNewInstance;
|
|
try
|
|
json := ObjectToJson(tempvoid, [woDontStoreDefault]);
|
|
PDocVariantData(@result)^.InitJsonInPlace(pointer(json), aOptions);
|
|
finally
|
|
tempvoid.Free;
|
|
end;
|
|
end;
|
|
|
|
|
|
{$ifdef HASITERATORS}
|
|
|
|
{ TDocVariantEnumeratorState }
|
|
|
|
procedure TDocVariantEnumeratorState.Void;
|
|
begin
|
|
After := nil;
|
|
Curr := nil;
|
|
end;
|
|
|
|
procedure TDocVariantEnumeratorState.Init(Values: PVariantArray; Count: PtrUInt);
|
|
begin
|
|
if Count = 0 then
|
|
Void
|
|
else
|
|
begin
|
|
Curr := pointer(Values);
|
|
After := @Values[Count];
|
|
dec(Curr);
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantEnumeratorState.MoveNext: boolean;
|
|
begin
|
|
inc(Curr);
|
|
result := PtrUInt(Curr) < PtrUInt(After); // Void = nil+1<nil = false
|
|
end;
|
|
|
|
{ TDocVariantFieldsEnumerator }
|
|
|
|
function TDocVariantFieldsEnumerator.GetCurrent: TDocVariantFields;
|
|
begin
|
|
result.Name := Name;
|
|
result.Value := State.Curr;
|
|
end;
|
|
|
|
function TDocVariantFieldsEnumerator.MoveNext: boolean;
|
|
begin
|
|
result := State.MoveNext;
|
|
if result and
|
|
Assigned(Name) then
|
|
inc(Name);
|
|
end;
|
|
|
|
function TDocVariantFieldsEnumerator.GetEnumerator: TDocVariantFieldsEnumerator;
|
|
begin
|
|
result := self;
|
|
end;
|
|
|
|
{ TDocVariantFieldNamesEnumerator }
|
|
|
|
function TDocVariantFieldNamesEnumerator.MoveNext: boolean;
|
|
begin
|
|
inc(Curr);
|
|
result := PtrUInt(Curr) < PtrUInt(After);
|
|
end;
|
|
|
|
function TDocVariantFieldNamesEnumerator.GetEnumerator: TDocVariantFieldNamesEnumerator;
|
|
begin
|
|
result := self;
|
|
end;
|
|
|
|
{ TDocVariantItemsEnumerator }
|
|
|
|
function TDocVariantItemsEnumerator.MoveNext: boolean;
|
|
begin
|
|
result := State.MoveNext;
|
|
end;
|
|
|
|
function TDocVariantItemsEnumerator.GetEnumerator: TDocVariantItemsEnumerator;
|
|
begin
|
|
result := self;
|
|
end;
|
|
|
|
{ TDocVariantObjectsEnumerator }
|
|
|
|
function TDocVariantObjectsEnumerator.MoveNext: boolean;
|
|
var
|
|
vt: cardinal;
|
|
vd: PVarData; // inlined while not DocVariant.IsOfType() + Value := _Safe()
|
|
begin
|
|
repeat
|
|
inc(State.Curr);
|
|
vd := pointer(State.Curr);
|
|
if PtrUInt(vd) >= PtrUInt(State.After) then
|
|
break;
|
|
repeat
|
|
vt := vd^.VType;
|
|
if vt = DocVariantVType then
|
|
begin
|
|
Value := pointer(vd);
|
|
result := true;
|
|
exit;
|
|
end;
|
|
if vt <> varVariantByRef then
|
|
break;
|
|
vd := vd^.VPointer;
|
|
until false;
|
|
until false;
|
|
result := false;
|
|
end;
|
|
|
|
function TDocVariantObjectsEnumerator.GetEnumerator: TDocVariantObjectsEnumerator;
|
|
begin
|
|
result := self;
|
|
end;
|
|
|
|
{$endif HASITERATORS}
|
|
|
|
|
|
{ TDocVariantData }
|
|
|
|
function TDocVariantData.GetValueIndex(const aName: RawUtf8): integer;
|
|
begin
|
|
result := GetValueIndex(Pointer(aName), Length(aName), IsCaseSensitive);
|
|
end;
|
|
|
|
function TDocVariantData.GetCapacity: integer;
|
|
begin
|
|
result := length(VValue);
|
|
end;
|
|
|
|
function TDocVariant.InternNames: TRawUtf8Interning;
|
|
begin
|
|
result := fInternNames;
|
|
if result = nil then
|
|
result := CreateInternNames;
|
|
end;
|
|
|
|
function TDocVariant.CreateInternNames: TRawUtf8Interning;
|
|
begin
|
|
fInternSafe.Lock;
|
|
try
|
|
if fInternNames = nil then
|
|
fInternNames := TRawUtf8Interning.Create;
|
|
finally
|
|
fInternSafe.UnLock;
|
|
end;
|
|
result := fInternNames;
|
|
end;
|
|
|
|
function TDocVariant.InternValues: TRawUtf8Interning;
|
|
begin
|
|
result := fInternValues;
|
|
if fInternValues = nil then
|
|
result := CreateInternValues;
|
|
end;
|
|
|
|
function TDocVariant.CreateInternValues: TRawUtf8Interning;
|
|
begin
|
|
fInternSafe.Lock;
|
|
try
|
|
if fInternValues = nil then
|
|
fInternValues := TRawUtf8Interning.Create;
|
|
finally
|
|
fInternSafe.UnLock;
|
|
end;
|
|
result := fInternValues;
|
|
end;
|
|
|
|
procedure TDocVariantData.InternalUniqueValueAt(aIndex: PtrInt);
|
|
begin
|
|
DocVariantType.InternValues.UniqueVariant(VValue[aIndex]);
|
|
end;
|
|
|
|
procedure InternalUniqueValue(aValue: PVariant); // local to this unit
|
|
begin
|
|
DocVariantType.InternValues.UniqueVariant(aValue^);
|
|
end;
|
|
|
|
procedure TDocVariantData.SetOptions(const opt: TDocVariantOptions);
|
|
begin
|
|
VOptions := TDocVariantOptions(word(cardinal(word(opt) and not _DVO) +
|
|
cardinal(word(VOptions) and _DVO)));
|
|
end;
|
|
|
|
procedure TDocVariantData.InitClone(const CloneFrom: TDocVariantData);
|
|
begin
|
|
TRttiVarData(self).VType := TRttiVarData(CloneFrom).VType and not (_DVO shl 16);
|
|
VCount := 0;
|
|
pointer(VName) := nil; // to avoid GPF
|
|
pointer(VValue) := nil;
|
|
end;
|
|
|
|
function TDocVariantData.InitFrom(const CloneFrom: TDocVariantData;
|
|
CloneValues, MakeUnique: boolean): PVariant;
|
|
begin
|
|
TRttiVarData(self).VType := TRttiVarData(CloneFrom).VType; // VType+VOptions
|
|
VCount := CloneFrom.VCount;
|
|
if MakeUnique then // new array, but byref names
|
|
DynArrayCopy(@VName, @CloneFrom.VName, TypeInfo(TRawUtf8DynArray))
|
|
else
|
|
VName := CloneFrom.VName; // byref copy of the whole array
|
|
if CloneValues then
|
|
if MakeUnique then // new array, but byref values
|
|
DynArrayCopy(@VValue, @CloneFrom.VValue, TypeInfo(TVariantDynArray))
|
|
else
|
|
VValue := CloneFrom.VValue // byref copy of the whole array
|
|
else
|
|
SetLength(VValue, VCount); // setup void values
|
|
result := pointer(VValue);
|
|
end;
|
|
|
|
procedure TDocVariantData.Init(const aOptions: TDocVariantOptions);
|
|
begin
|
|
TRttiVarData(self).VType := DocVariantVType + // VType+VOptions
|
|
cardinal(word(aOptions) and not _DVO) shl 16;
|
|
VCount := 0;
|
|
pointer(VName) := nil; // to avoid GPF when mapped within a TVarData/variant
|
|
pointer(VValue) := nil;
|
|
end;
|
|
|
|
procedure TDocVariantData.Init(const aOptions: TDocVariantOptions;
|
|
aKind: TDocVariantKind);
|
|
begin // dvUndefined=0 dvArray=1 dvObject=2 -> [dvoIsArray]=1 [dvoIsObject]=2
|
|
TRttiVarData(self).VType := DocVariantVType + // VType+VOptions
|
|
cardinal((word(aOptions) and not _DVO) + ord(aKind)) shl 16;
|
|
VCount := 0;
|
|
pointer(VName) := nil; // to avoid GPF
|
|
pointer(VValue) := nil;
|
|
end;
|
|
|
|
procedure TDocVariantData.Init(aModel: TDocVariantModel; aKind: TDocVariantKind);
|
|
begin
|
|
Init(JSON_[aModel], aKind);
|
|
end;
|
|
|
|
procedure TDocVariantData.InitFast(aKind: TDocVariantKind);
|
|
begin
|
|
TVarData(self) := DV_FAST[aKind];
|
|
end;
|
|
|
|
procedure TDocVariantData.InitFast(InitialCapacity: integer;
|
|
aKind: TDocVariantKind);
|
|
begin
|
|
TVarData(self) := DV_FAST[aKind];
|
|
if aKind = dvObject then
|
|
SetLength(VName, InitialCapacity);
|
|
SetLength(VValue, InitialCapacity);
|
|
end;
|
|
|
|
procedure TDocVariantData.InitObject(const NameValuePairs: array of const;
|
|
aOptions: TDocVariantOptions);
|
|
begin
|
|
Init(aOptions, dvObject);
|
|
AddNameValuesToObject(NameValuePairs);
|
|
end;
|
|
|
|
procedure TDocVariantData.InitObject(const NameValuePairs: array of const;
|
|
Model: TDocVariantModel);
|
|
begin
|
|
Init(Model, dvObject);
|
|
AddNameValuesToObject(NameValuePairs);
|
|
end;
|
|
|
|
procedure TDocVariantData.InternalSetVarRec(aIndex: PtrInt; const aValue: TVarRec);
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := @VValue[aIndex];
|
|
if Has(dvoValueCopiedByReference) or
|
|
(aValue.VType <> vtVariant) then
|
|
VarRecToVariant(aValue, v^)
|
|
else
|
|
SetVariantByValue(aValue.VVariant^, v^);
|
|
if Has(dvoInternValues) then
|
|
InternalUniqueValueAt(aIndex);
|
|
end;
|
|
|
|
procedure TDocVariantData.Include(dvo: TDocVariantOption);
|
|
begin
|
|
TRttiVarData(self).VType := TRttiVarData(self).VType or
|
|
cardinal(1 shl (ord(dvo) + 16));
|
|
end;
|
|
|
|
procedure TDocVariantData.AddNameValuesToObject(
|
|
const NameValuePairs: array of const);
|
|
var
|
|
n, arg, ndx: PtrInt;
|
|
begin
|
|
n := length(NameValuePairs);
|
|
if (n = 0) or
|
|
(n and 1 = 1) or
|
|
IsArray then
|
|
exit; // nothing to add
|
|
Include(dvoIsObject);
|
|
n := n shr 1;
|
|
ndx := n + VCount;
|
|
if length(VValue) < ndx then
|
|
begin
|
|
SetLength(VValue, ndx);
|
|
SetLength(VName, ndx);
|
|
end;
|
|
ndx := VCount;
|
|
for arg := 0 to n - 1 do
|
|
begin
|
|
VarRecToUtf8(NameValuePairs[arg * 2], VName[ndx]);
|
|
if Has(dvoInternNames) then
|
|
DocVariantType.InternNames.UniqueText(VName[ndx]);
|
|
InternalSetVarRec(ndx, NameValuePairs[arg * 2 + 1]);
|
|
inc(ndx);
|
|
end;
|
|
inc(VCount, n);
|
|
end;
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
procedure TDocVariantData.AddOrUpdateNameValuesToObject(
|
|
const NameValuePairs: array of const);
|
|
begin
|
|
Update(NameValuePairs);
|
|
end;
|
|
{$endif PUREMORMOT2}
|
|
|
|
procedure TDocVariantData.Update(const NameValuePairs: array of const);
|
|
var
|
|
n, arg: PtrInt;
|
|
nam: RawUtf8;
|
|
val: Variant;
|
|
begin
|
|
n := length(NameValuePairs);
|
|
if (n = 0) or
|
|
(n and 1 = 1) or
|
|
IsArray then
|
|
exit; // nothing to add
|
|
for arg := 0 to (n shr 1) - 1 do
|
|
begin
|
|
VarRecToUtf8(NameValuePairs[arg * 2], nam);
|
|
VarRecToVariant(NameValuePairs[arg * 2 + 1], val);
|
|
AddOrUpdateValue(nam, val)
|
|
end;
|
|
end;
|
|
|
|
procedure TDocVariantData.AddOrUpdateObject(const NewValues: variant;
|
|
OnlyAddMissing: boolean; RecursiveUpdate: boolean);
|
|
var
|
|
n, idx: PtrInt;
|
|
new: PDocVariantData;
|
|
wasAdded: boolean;
|
|
begin
|
|
new := _Safe(NewValues);
|
|
if not IsArray and
|
|
not new^.IsArray then
|
|
for n := 0 to new^.Count - 1 do
|
|
begin
|
|
idx := AddOrUpdateValue(
|
|
new^.names[n], new^.Values[n], @wasAdded, OnlyAddMissing);
|
|
if RecursiveUpdate and
|
|
not wasAdded then
|
|
TDocVariantData(Values[idx]).AddOrUpdateObject(
|
|
new^.Values[n], OnlyAddMissing, true);
|
|
end;
|
|
end;
|
|
|
|
procedure TDocVariantData.InitArray(const aItems: array of const;
|
|
aOptions: TDocVariantOptions);
|
|
var
|
|
arg: PtrInt;
|
|
begin
|
|
Init(aOptions, dvArray);
|
|
if high(aItems) < 0 then
|
|
exit;
|
|
VCount := length(aItems);
|
|
SetLength(VValue, VCount);
|
|
for arg := 0 to high(aItems) do
|
|
InternalSetVarRec(arg, aItems[arg]);
|
|
end;
|
|
|
|
procedure TDocVariantData.InitArray(const aItems: array of const;
|
|
aModel: TDocVariantModel);
|
|
begin
|
|
InitArray(aItems, JSON_[aModel]);
|
|
end;
|
|
|
|
procedure TDocVariantData.InitArrayFromVariants(const aItems: TVariantDynArray;
|
|
aOptions: TDocVariantOptions; aItemsCopiedByReference: boolean; aCount: integer);
|
|
begin
|
|
if aItems = nil then
|
|
TRttiVarData(self).VType := varNull
|
|
else
|
|
begin
|
|
Init(aOptions, dvArray);
|
|
if aCount < 0 then
|
|
VCount := length(aItems)
|
|
else
|
|
VCount := aCount;
|
|
VValue := aItems; // fast by-reference copy of VValue[] array
|
|
if not aItemsCopiedByReference then
|
|
InitCopy(variant(self), aOptions);
|
|
end;
|
|
end;
|
|
|
|
procedure TDocVariantData.InitArrayFromObjectValues(const aObject: variant;
|
|
aOptions: TDocVariantOptions; aItemsCopiedByReference: boolean);
|
|
var
|
|
dv: PDocVariantData;
|
|
begin
|
|
if _SafeObject(aObject, dv) then
|
|
InitArrayFromVariants(dv^.Values, aOptions, aItemsCopiedByReference, dv^.Count)
|
|
else
|
|
TRttiVarData(self).VType := varNull;
|
|
end;
|
|
|
|
procedure TDocVariantData.InitArrayFromObjectNames(const aObject: variant;
|
|
aOptions: TDocVariantOptions; aItemsCopiedByReference: boolean);
|
|
var
|
|
dv: PDocVariantData;
|
|
begin
|
|
if _SafeObject(aObject, dv) then
|
|
InitArrayFrom(dv^.Names, aOptions, dv^.Count)
|
|
else
|
|
TRttiVarData(self).VType := varNull;
|
|
end;
|
|
|
|
procedure TDocVariantData.InitArrayFromCsv(const aCsv: RawUtf8;
|
|
aOptions: TDocVariantOptions; aSeparator: AnsiChar;
|
|
aTrimItems, aAddVoidItems: boolean; aQuote: AnsiChar);
|
|
var
|
|
tmp: TRawUtf8DynArray;
|
|
begin
|
|
if aSeparator = #0 then
|
|
aSeparator := CsvGuessSeparator(aCsv); // separator-tolerant
|
|
CsvToRawUtf8DynArray(
|
|
pointer(aCsv), tmp, aSeparator, aTrimItems, aAddVoidItems, aQuote);
|
|
InitArrayFrom(tmp, aOptions);
|
|
end;
|
|
|
|
procedure _FromText(opt: TDocVariantOptions; v: PVariant; const t: RawUtf8);
|
|
begin
|
|
if not GetVariantFromNotStringJson(
|
|
pointer(t), PVarData(v)^, dvoAllowDoubleValue in opt) then
|
|
if dvoInternValues in opt then
|
|
DocVariantType.InternValues.UniqueVariant(v^, t)
|
|
else
|
|
RawUtf8ToVariant(t, v^);
|
|
end;
|
|
|
|
procedure TDocVariantData.InitArrayFromCsvFile(const aCsv: RawUtf8;
|
|
aOptions: TDocVariantOptions; aSeparator, aQuote: AnsiChar);
|
|
var
|
|
c: PUtf8Char;
|
|
nam, tmp: TRawUtf8DynArray;
|
|
v: PDocVariantData;
|
|
line: RawUtf8;
|
|
n, j, t: PtrInt;
|
|
begin
|
|
n := 0;
|
|
Init(aOptions, dvArray);
|
|
c := pointer(aCsv);
|
|
while c <> nil do
|
|
begin
|
|
line := GetNextLine(c, c);
|
|
if line = '' then
|
|
continue;
|
|
if (tmp = nil) and
|
|
(aSeparator = #0) then
|
|
aSeparator := CsvGuessSeparator(line); // separator-tolerant
|
|
tmp := nil;
|
|
CsvToRawUtf8DynArray(pointer(line), tmp, aSeparator, false, true, aQuote);
|
|
if tmp <> nil then
|
|
if nam = nil then
|
|
begin
|
|
nam := tmp; // first line are field/column names
|
|
n := length(nam);
|
|
end
|
|
else
|
|
begin
|
|
j := InternalAdd('', -1); // new row of data
|
|
v := @VValue[j]; // in two lines for FPC
|
|
v^.Init(aOptions, dvObject);
|
|
v^.VName := nam;
|
|
v^.VCount := n;
|
|
SetLength(v^.VValue, n);
|
|
t := length(tmp);
|
|
if t > n then
|
|
t := n; // allow too many or missing last columns
|
|
for j := 0 to t - 1 do
|
|
_FromText(aOptions, @v^.VValue[j], tmp[j]); // recognize numbers
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TDocVariantData.InitArrayFrom(const aSource: TDocVariantData;
|
|
aOptions: TDocVariantOptions; aOffset, aLimit: integer);
|
|
begin
|
|
Init(aOptions, dvArray);
|
|
VCount := aLimit;
|
|
if not aSource.RangeVoid(aOffset, VCount) then // not void
|
|
VValue := copy(aSource.VValue, aOffset, VCount); // new array, byref values
|
|
end;
|
|
|
|
function _InitArray(out aDest: TDocVariantData; aOptions: TDocVariantOptions;
|
|
aCount: integer; const aItems): PRttiVarData;
|
|
begin
|
|
if aCount < 0 then
|
|
aCount := length(TByteDynArray(aItems));
|
|
if aCount = 0 then
|
|
begin
|
|
TRttiVarData(aDest).VType := varNull;
|
|
result := nil;
|
|
exit;
|
|
end;
|
|
{%H-}aDest.Init(aOptions, dvArray);
|
|
aDest.VCount := aCount;
|
|
SetLength(aDest.VValue, aCount);
|
|
result := pointer(aDest.VValue);
|
|
end;
|
|
|
|
procedure TDocVariantData.InitArrayFromObjArray(const ObjArray;
|
|
aOptions: TDocVariantOptions; aWriterOptions: TTextWriterWriteObjectOptions;
|
|
aCount: integer);
|
|
var
|
|
ndx: PtrInt;
|
|
aItems: TObjectDynArray absolute ObjArray;
|
|
begin
|
|
_InitArray(self, aOptions, aCount, aItems);
|
|
for ndx := 0 to VCount - 1 do
|
|
ObjectToVariant(aItems[ndx], VValue[ndx], aWriterOptions);
|
|
end;
|
|
|
|
procedure TDocVariantData.InitArrayFrom(const aItems: TRawUtf8DynArray;
|
|
aOptions: TDocVariantOptions; aCount: integer);
|
|
var
|
|
ndx: PtrInt;
|
|
v: PRttiVarData;
|
|
begin
|
|
v := _InitArray(self, aOptions, aCount, aItems);
|
|
for ndx := 0 to VCount - 1 do
|
|
begin
|
|
v^.VType := varString;
|
|
RawUtf8(v^.Data.VAny) := aItems[ndx];
|
|
inc(v);
|
|
end;
|
|
end;
|
|
|
|
procedure TDocVariantData.InitArrayFrom(const aItems: TIntegerDynArray;
|
|
aOptions: TDocVariantOptions; aCount: integer);
|
|
var
|
|
ndx: PtrInt;
|
|
v: PRttiVarData;
|
|
begin
|
|
v := _InitArray(self, aOptions, aCount, aItems);
|
|
for ndx := 0 to VCount - 1 do
|
|
begin
|
|
v^.VType := varInteger;
|
|
v^.Data.VInteger := aItems[ndx];
|
|
inc(v);
|
|
end;
|
|
end;
|
|
|
|
procedure TDocVariantData.InitArrayFrom(const aItems: TInt64DynArray;
|
|
aOptions: TDocVariantOptions; aCount: integer);
|
|
var
|
|
ndx: PtrInt;
|
|
v: PRttiVarData;
|
|
begin
|
|
v := _InitArray(self, aOptions, aCount, aItems);
|
|
for ndx := 0 to VCount - 1 do
|
|
begin
|
|
v^.VType := varInt64;
|
|
v^.Data.VInt64 := aItems[ndx];
|
|
inc(v);
|
|
end;
|
|
end;
|
|
|
|
procedure TDocVariantData.InitArrayFrom(const aItems: TDoubleDynArray;
|
|
aOptions: TDocVariantOptions; aCount: integer);
|
|
var
|
|
ndx: PtrInt;
|
|
v: PRttiVarData;
|
|
begin
|
|
v := _InitArray(self, aOptions, aCount, aItems);
|
|
for ndx := 0 to VCount - 1 do
|
|
begin
|
|
v^.VType := varDouble;
|
|
v^.Data.VDouble := aItems[ndx];
|
|
inc(v);
|
|
end;
|
|
end;
|
|
|
|
procedure TDocVariantData.InitArrayFrom(var aItems; ArrayInfo: PRttiInfo;
|
|
aOptions: TDocVariantOptions; ItemsCount: PInteger);
|
|
var
|
|
da: TDynArray;
|
|
begin
|
|
da.Init(ArrayInfo, aItems, ItemsCount);
|
|
InitArrayFrom(da, aOptions);
|
|
end;
|
|
|
|
procedure TDocVariantData.InitArrayFrom(const aItems: TDynArray;
|
|
aOptions: TDocVariantOptions);
|
|
var
|
|
n: integer;
|
|
pb: PByte;
|
|
v: PVarData;
|
|
item: TRttiCustom;
|
|
json: RawUtf8;
|
|
begin
|
|
Init(aOptions, dvArray);
|
|
n := aItems.Count;
|
|
item := aItems.Info.ArrayRtti;
|
|
if (n = 0) or
|
|
(item = nil) then
|
|
exit;
|
|
if item.Kind in (rkRecordOrDynArrayTypes + [rkClass]) then
|
|
begin
|
|
// use temporary non-expanded JSON conversion for complex nested content
|
|
aItems.SaveToJson(json, [twoNonExpandedArrays]);
|
|
if (json <> '') and
|
|
(json[1] = '{') then
|
|
// should be a non-expanded array, not JSON_BASE64_MAGIC_QUOTE_C
|
|
InitArrayFromResults(pointer(json), length(json), aOptions);
|
|
end
|
|
else
|
|
begin
|
|
// handle array of simple types
|
|
VCount := n;
|
|
SetLength(VValue, n);
|
|
pb := aItems.Value^;
|
|
v := pointer(VValue);
|
|
repeat
|
|
inc(pb, item.ValueToVariant(pb, v^));
|
|
inc(v);
|
|
dec(n);
|
|
until n = 0;
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.InitArrayFromResults(Json: PUtf8Char; JsonLen: PtrInt;
|
|
aOptions: TDocVariantOptions): boolean;
|
|
var
|
|
J: PUtf8Char;
|
|
fieldcount, rowcount, capa, r, f: PtrInt;
|
|
info: TGetJsonField;
|
|
dv: PDocVariantData;
|
|
val: PVariant;
|
|
proto: TDocVariantData;
|
|
begin
|
|
result := false;
|
|
Init(aOptions, dvArray);
|
|
info.Json := GotoNextNotSpace(Json);
|
|
if IsNotExpandedBuffer(info.Json, Json + JsonLen, fieldcount, rowcount) then
|
|
begin
|
|
// A. Not Expanded (more optimized) format as array of values
|
|
// {"fieldCount":2,"values":["f1","f2","1v1",1v2,"2v1",2v2...],"rowCount":20}
|
|
// 1. check rowcount and fieldcount
|
|
if (rowcount < 0) or // IsNotExpandedBuffer() detected invalid input
|
|
(fieldcount = 0) then
|
|
exit;
|
|
// 2. initialize the object prototype with the trailing field names
|
|
proto.Init(aOptions, dvObject);
|
|
proto.Capacity := fieldcount;
|
|
for f := 1 to fieldcount do
|
|
begin
|
|
info.GetJsonField;
|
|
if not info.WasString then
|
|
exit; // should start with field names
|
|
proto.AddValue(info.Value, info.ValueLen, null); // set proper field name
|
|
end;
|
|
// 3. fill all nested objects from incoming values
|
|
SetLength(VValue, rowcount);
|
|
dv := pointer(VValue);
|
|
for r := 1 to rowcount do
|
|
begin
|
|
val := dv^.InitFrom(proto, {values=}false); // names byref + void values
|
|
for f := 1 to fieldcount do
|
|
begin
|
|
JsonToAnyVariant(val^, info, @aOptions);
|
|
inc(val);
|
|
end;
|
|
if info.Json = nil then
|
|
exit;
|
|
inc(dv); // next object
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
// B. Expanded format as array of objects (each with field names)
|
|
// [{"f1":"1v1","f2":1v2},{"f2":"2v1","f2":2v2}...]
|
|
// 1. get first object (will reuse its field names)
|
|
info.Json := GotoFieldCountExpanded(info.Json);
|
|
if (info.Json = nil) or
|
|
(info.Json^ = ']') then
|
|
exit; // [] -> valid, but void data
|
|
info.Json := proto.InitJsonInPlace(info.Json, aOptions, @info.EndOfObject);
|
|
if info.Json = nil then
|
|
exit;
|
|
if info.EndOfObject = ']' then
|
|
begin
|
|
AddItem(variant(proto)); // single item array
|
|
result := true;
|
|
exit;
|
|
end;
|
|
rowcount := 0;
|
|
capa := 16;
|
|
SetLength(VValue, capa);
|
|
dv := pointer(VValue);
|
|
dv^ := proto;
|
|
// 2. get values (assume fieldcount are always the same as in the first object)
|
|
repeat
|
|
J := info.Json;
|
|
while (J^ <> '{') and
|
|
(J^ <> ']') do // go to next object beginning
|
|
if J^ = #0 then
|
|
exit
|
|
else
|
|
inc(J);
|
|
inc(rowcount);
|
|
if J^ = ']' then
|
|
break;
|
|
info.Json := J + 1; // jmp '}'
|
|
if rowcount = capa then
|
|
begin
|
|
capa := NextGrow(capa);
|
|
SetLength(VValue, capa);
|
|
dv := @VValue[rowcount];
|
|
end
|
|
else
|
|
inc(dv);
|
|
val := dv^.InitFrom(proto, {values=}false);
|
|
for f := 1 to proto.Count do
|
|
begin
|
|
info.Json := GotoEndJsonItemString(info.Json); // ignore field names
|
|
if info.Json = nil then
|
|
exit;
|
|
inc(info.Json); // ignore jcEndOfJsonFieldOr0
|
|
JsonToAnyVariant(val^, info, @aOptions);
|
|
if info.Json = nil then
|
|
exit;
|
|
inc(val);
|
|
end;
|
|
if info.EndOfObject<> '}' then
|
|
exit;
|
|
until false;
|
|
end;
|
|
VCount := rowcount;
|
|
result := true;
|
|
end;
|
|
|
|
function TDocVariantData.InitArrayFromResults(const Json: RawUtf8;
|
|
aOptions: TDocVariantOptions): boolean;
|
|
var
|
|
tmp: TSynTempBuffer;
|
|
begin
|
|
tmp.Init(Json);
|
|
try
|
|
result := InitArrayFromResults(tmp.buf, tmp.len, aOptions);
|
|
finally
|
|
tmp.Done;
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.InitArrayFromResults(const Json: RawUtf8;
|
|
aModel: TDocVariantModel): boolean;
|
|
begin
|
|
result := InitArrayFromResults(Json, JSON_[aModel]);
|
|
end;
|
|
|
|
procedure TDocVariantData.InitObjectFromVariants(const aNames: TRawUtf8DynArray;
|
|
const aValues: TVariantDynArray; aOptions: TDocVariantOptions);
|
|
begin
|
|
if (aNames = nil) or
|
|
(length(aValues) <> PDALen(PAnsiChar(aNames) - _DALEN)^ + _DAOFF) then
|
|
TRttiVarData(self).VType := varNull
|
|
else
|
|
begin
|
|
Init(aOptions, dvObject);
|
|
VCount := PDALen(PAnsiChar(aNames) - _DALEN)^ + _DAOFF;
|
|
VName := aNames; // fast by-reference copy of VName[] and VValue[]
|
|
VValue := aValues;
|
|
end;
|
|
end;
|
|
|
|
procedure TDocVariantData.InitObjectFromPath(const aPath: RawUtf8;
|
|
const aValue: variant; aOptions: TDocVariantOptions; aPathDelim: AnsiChar);
|
|
var
|
|
right: RawUtf8;
|
|
begin
|
|
if aPath <> '' then
|
|
begin
|
|
Init(aOptions, dvObject);
|
|
VCount := 1;
|
|
SetLength(VName, 1);
|
|
SetLength(VValue, 1);
|
|
Split(aPath, aPathDelim, VName[0], right);
|
|
if right = '' then
|
|
VValue[0] := aValue
|
|
else
|
|
PDocVariantData(@VValue[0])^.InitObjectFromPath(
|
|
right, aValue, aOptions, aPathDelim);
|
|
exit;
|
|
end;
|
|
TRttiVarData(self).VType := varNull;
|
|
end;
|
|
|
|
function TDocVariantData.InitJsonInPlace(Json: PUtf8Char;
|
|
aOptions: TDocVariantOptions; aEndOfObject: PUtf8Char): PUtf8Char;
|
|
var
|
|
info: TGetJsonField;
|
|
Name: PUtf8Char;
|
|
NameLen: integer;
|
|
n, cap: PtrInt;
|
|
Val: PVariant;
|
|
intnames, intvalues: TRawUtf8Interning;
|
|
begin
|
|
Init(aOptions);
|
|
result := nil;
|
|
if Json = nil then
|
|
exit;
|
|
if Has(dvoInternValues) then
|
|
intvalues := DocVariantType.InternValues
|
|
else
|
|
intvalues := nil;
|
|
while (Json^ <= ' ') and
|
|
(Json^ <> #0) do
|
|
inc(Json);
|
|
case Json^ of
|
|
'[':
|
|
begin
|
|
repeat
|
|
inc(Json);
|
|
if Json^ = #0 then
|
|
exit;
|
|
until Json^ > ' ';
|
|
Include(dvoIsArray);
|
|
if Json^ = ']' then
|
|
// void but valid input array
|
|
Json := GotoNextNotSpace(Json + 1)
|
|
else
|
|
begin
|
|
if Has(dvoJsonParseDoNotGuessCount) then
|
|
cap := 8 // with a lot of nested objects -> best to ignore
|
|
else
|
|
begin
|
|
// guess of the Json array items count - prefetch up to 64KB of input
|
|
cap := abs(JsonArrayCount(Json, Json + JSON_PREFETCH));
|
|
if cap = 0 then
|
|
exit; // invalid content
|
|
end;
|
|
SetLength(VValue, cap);
|
|
Val := pointer(VValue);
|
|
n := 0;
|
|
info.Json := Json;
|
|
repeat
|
|
if n = cap then
|
|
begin
|
|
// grow if our initial guess was aborted due to huge input
|
|
cap := NextGrow(cap);
|
|
SetLength(VValue, cap);
|
|
Val := @VValue[n];
|
|
end;
|
|
// unserialize the next item
|
|
JsonToAnyVariant(val^, info, @VOptions);
|
|
if info.Json = nil then
|
|
break; // invalid input
|
|
if intvalues <> nil then
|
|
intvalues.UniqueVariant(val^);
|
|
inc(Val);
|
|
inc(n);
|
|
until info.EndOfObject = ']';
|
|
Json := info.Json;
|
|
if Json = nil then
|
|
begin
|
|
// invalid input
|
|
VValue := nil;
|
|
exit;
|
|
end;
|
|
// ok - but no SetLength(..,VCount) if NextGrow() on huge input
|
|
VCount := n;
|
|
end;
|
|
end;
|
|
'{':
|
|
begin
|
|
repeat
|
|
inc(Json);
|
|
if Json^ = #0 then
|
|
exit;
|
|
until Json^ > ' ';
|
|
Include(dvoIsObject);
|
|
if Json^ = '}' then
|
|
// void but valid input object
|
|
Json := GotoNextNotSpace(Json + 1)
|
|
else
|
|
begin
|
|
if Has(dvoJsonParseDoNotGuessCount) then
|
|
cap := 4 // with a lot of nested documents -> best to ignore
|
|
else
|
|
begin
|
|
// guess of the Json object properties count - prefetch up to 64KB
|
|
cap := JsonObjectPropCount(Json, Json + JSON_PREFETCH);
|
|
if cap = 0 then
|
|
exit // invalid content (was <0 if early abort)
|
|
else if cap < 0 then
|
|
begin // nested or huge objects are evil -> no more guess
|
|
cap := -cap;
|
|
Include(dvoJsonParseDoNotGuessCount);
|
|
end;
|
|
end;
|
|
if Has(dvoInternNames) then
|
|
intnames := DocVariantType.InternNames
|
|
else
|
|
intnames := nil;
|
|
SetLength(VValue, cap);
|
|
Val := pointer(VValue);
|
|
SetLength(VName, cap);
|
|
n := 0;
|
|
info.Json := Json;
|
|
repeat
|
|
// see http://docs.mongodb.org/manual/reference/mongodb-extended-Json
|
|
Name := GetJsonPropName(info.Json, @NameLen);
|
|
if Name = nil then
|
|
break; // invalid input
|
|
if n = cap then
|
|
begin
|
|
// grow if our initial guess was aborted due to huge input
|
|
cap := NextGrow(cap);
|
|
SetLength(VName, cap);
|
|
SetLength(VValue, cap);
|
|
Val := @VValue[n];
|
|
end;
|
|
JsonToAnyVariant(Val^, info, @VOptions);
|
|
if info.Json = nil then
|
|
if info.EndOfObject = '}' then // valid object end
|
|
info.Json := @NULCHAR
|
|
else
|
|
break; // invalid input
|
|
if NameLen <> 0 then // we just ignore void "":xxx field names
|
|
begin
|
|
if intnames <> nil then
|
|
intnames.Unique(VName[n], Name, NameLen)
|
|
else
|
|
FastSetString(VName[n], Name, NameLen);
|
|
if intvalues <> nil then
|
|
intvalues.UniqueVariant(Val^);
|
|
inc(n);
|
|
inc(Val);
|
|
end;
|
|
until info.EndOfObject = '}';
|
|
Json := info.Json;
|
|
if (Name = nil) or
|
|
(Json = nil) then
|
|
begin
|
|
// invalid input
|
|
VName := nil;
|
|
VValue := nil;
|
|
exit;
|
|
end;
|
|
// ok - but no SetLength(..,VCount) if NextGrow() on huge input
|
|
VCount := n;
|
|
end;
|
|
end;
|
|
'n',
|
|
'N':
|
|
begin
|
|
if IdemPChar(Json + 1, 'ULL') then
|
|
begin
|
|
Include(dvoIsObject);
|
|
result := GotoNextNotSpace(Json + 4);
|
|
end;
|
|
exit;
|
|
end;
|
|
else
|
|
exit;
|
|
end;
|
|
while (Json^ <= ' ') and
|
|
(Json^ <> #0) do
|
|
inc(Json);
|
|
if aEndOfObject <> nil then
|
|
aEndOfObject^ := Json^;
|
|
if Json^ <> #0 then
|
|
repeat
|
|
inc(Json)
|
|
until (Json^ = #0) or
|
|
(Json^ > ' ');
|
|
result := Json; // indicates successfully parsed
|
|
end;
|
|
|
|
function TDocVariantData.InitJson(const Json: RawUtf8;
|
|
aOptions: TDocVariantOptions): boolean;
|
|
var
|
|
tmp: TSynTempBuffer;
|
|
begin
|
|
if Json = '' then
|
|
result := false
|
|
else
|
|
begin
|
|
tmp.Init(Json);
|
|
try
|
|
result := InitJsonInPlace(tmp.buf, aOptions) <> nil;
|
|
finally
|
|
tmp.Done;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.InitJson(const Json: RawUtf8; aModel: TDocVariantModel): boolean;
|
|
begin
|
|
result := InitJson(Json, JSON_[aModel]);
|
|
end;
|
|
|
|
function TDocVariantData.InitJsonFromFile(const FileName: TFileName;
|
|
aOptions: TDocVariantOptions): boolean;
|
|
begin
|
|
result := InitJsonInPlace(pointer(RawUtf8FromFile(FileName)), aOptions) <> nil;
|
|
end;
|
|
|
|
procedure TDocVariantData.InitFromPairs(aPairs: PUtf8Char;
|
|
aOptions: TDocVariantOptions; NameValueSep, ItemSep: AnsiChar; DoTrim: boolean);
|
|
var
|
|
n, v: RawUtf8;
|
|
val: variant;
|
|
begin
|
|
Init(aOptions, dvObject);
|
|
while aPairs <> nil do
|
|
begin
|
|
GetNextItem(aPairs, NameValueSep, n);
|
|
if ItemSep = #10 then
|
|
GetNextItemTrimedCRLF(aPairs, v)
|
|
else
|
|
GetNextItem(aPairs, ItemSep, v);
|
|
if DoTrim then
|
|
TrimSelf(v);
|
|
if n = '' then
|
|
break;
|
|
RawUtf8ToVariant(v, val);
|
|
AddValue(n, val);
|
|
end;
|
|
end;
|
|
|
|
procedure TDocVariantData.InitFromPairs(const aPairs: RawUtf8;
|
|
aOptions: TDocVariantOptions; NameValueSep, ItemSep: AnsiChar; DoTrim: boolean);
|
|
begin
|
|
InitFromPairs(pointer(aPairs), aOptions, NameValueSep, ItemSep, DoTrim);
|
|
end;
|
|
|
|
procedure TDocVariantData.InitCopy(const SourceDocVariant: variant;
|
|
aOptions: TDocVariantOptions);
|
|
var
|
|
ndx: PtrInt;
|
|
vt: cardinal;
|
|
Source: PDocVariantData;
|
|
SourceVValue: TVariantDynArray;
|
|
Handler: TCustomVariantType;
|
|
v: PVarData;
|
|
vv: PVariant;
|
|
begin
|
|
with TVarData(SourceDocVariant) do
|
|
if cardinal(VType) = varVariantByRef then
|
|
Source := VPointer
|
|
else
|
|
Source := @SourceDocVariant;
|
|
if cardinal(Source^.VType) <> DocVariantVType then
|
|
raise EDocVariant.CreateUtf8(
|
|
'Unexpected InitCopy(var%)', [VariantTypeName(PVarData(Source))^]);
|
|
SourceVValue := Source^.VValue; // local fast per-reference copy
|
|
if Source <> @self then
|
|
begin
|
|
VType := Source^.VType;
|
|
VCount := Source^.VCount;
|
|
pointer(VName) := nil; // avoid GPF
|
|
pointer(VValue) := nil;
|
|
aOptions := aOptions - [dvoIsArray, dvoIsObject]; // may not match Source
|
|
if Source^.IsArray then
|
|
system.include(aOptions, dvoIsArray)
|
|
else if Source^.IsObject then
|
|
begin
|
|
system.include(aOptions, dvoIsObject);
|
|
SetLength(VName, VCount);
|
|
for ndx := 0 to VCount - 1 do
|
|
VName[ndx] := Source^.VName[ndx]; // manual copy is needed
|
|
if (dvoInternNames in aOptions) and
|
|
not Source^.Has(dvoInternNames) then
|
|
with DocVariantType.InternNames do
|
|
for ndx := 0 to VCount - 1 do
|
|
UniqueText(VName[ndx]);
|
|
end;
|
|
VOptions := aOptions;
|
|
end
|
|
else
|
|
begin
|
|
SetOptions(aOptions);
|
|
VariantDynArrayClear(VValue); // full copy of all values
|
|
end;
|
|
if VCount > 0 then
|
|
begin
|
|
SetLength(VValue, VCount);
|
|
v := pointer(SourceVValue);
|
|
vv := pointer(VValue);
|
|
ndx := VCount;
|
|
repeat
|
|
repeat
|
|
vt := v^.VType;
|
|
if vt <> varVariantByRef then
|
|
break;
|
|
v := v^.VPointer;
|
|
until false;
|
|
if vt < varFirstCustom then
|
|
// simple string/number types copy
|
|
vv^ := variant(v^)
|
|
else if vt = DocVariantVType then
|
|
// direct recursive copy for TDocVariant
|
|
PDocVariantData(vv)^.InitCopy(variant(v^), VOptions)
|
|
else if FindCustomVariantType(vt, Handler) then
|
|
if Handler.InheritsFrom(TSynInvokeableVariantType) then
|
|
TSynInvokeableVariantType(Handler).CopyByValue(PVarData(vv)^, v^)
|
|
else
|
|
Handler.Copy(PVarData(vv)^, v^, false)
|
|
else
|
|
vv^ := variant(v^); // default copy
|
|
inc(v);
|
|
inc(vv);
|
|
dec(ndx);
|
|
until ndx = 0;
|
|
if Has(dvoInternValues) then
|
|
begin
|
|
ndx := VCount;
|
|
vv := pointer(VValue);
|
|
with DocVariantType.InternValues do
|
|
repeat
|
|
UniqueVariant(vv^);
|
|
inc(vv);
|
|
dec(ndx);
|
|
until ndx = 0;
|
|
end;
|
|
end;
|
|
VariantDynArrayClear(SourceVValue);
|
|
end;
|
|
|
|
procedure TDocVariantData.Void;
|
|
begin
|
|
VCount := 0;
|
|
if VName <> nil then
|
|
FastDynArrayClear(@VName, TypeInfo(RawUtf8));
|
|
if VValue <> nil then
|
|
FastDynArrayClear(@VValue, TypeInfo(variant));
|
|
end;
|
|
|
|
procedure TDocVariantData.Clear;
|
|
begin
|
|
if cardinal(VType) = DocVariantVType then
|
|
ClearFast
|
|
else
|
|
VarClear(variant(self));
|
|
end;
|
|
|
|
procedure TDocVariantData.Reset;
|
|
begin
|
|
VOptions := VOptions - [dvoIsArray, dvoIsObject];
|
|
Void;
|
|
end;
|
|
|
|
procedure TDocVariantData.FillZero;
|
|
var
|
|
n: integer;
|
|
v: PVariant;
|
|
begin
|
|
n := VCount;
|
|
v := pointer(VValue);
|
|
if n <> 0 then
|
|
repeat
|
|
mormot.core.variants.FillZero(v^);
|
|
inc(v);
|
|
dec(n);
|
|
until n = 0;
|
|
Reset;
|
|
end;
|
|
|
|
function TDocVariantData.GetModel(out model: TDocVariantModel): boolean;
|
|
var
|
|
opt: TDocVariantOptions;
|
|
ndx: PtrInt;
|
|
begin
|
|
opt := VOptions - [dvoIsArray, dvoIsObject, dvoJsonParseDoNotGuessCount];
|
|
ndx := WordScanIndex(@JSON_, ord(high(TDocVariantModel)) + 1, word(opt));
|
|
if ndx < 0 then
|
|
result := false
|
|
else
|
|
begin
|
|
model := TDocVariantModel(ndx);
|
|
result := true;
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.RangeVoid(var Offset, Limit: integer): boolean;
|
|
var
|
|
n, l: integer;
|
|
begin
|
|
result := true; // void
|
|
n := Count;
|
|
if Offset < 0 then
|
|
begin
|
|
inc(Offset, n);
|
|
if Offset < 0 then
|
|
Offset := 0;
|
|
end;
|
|
if Limit = 0 then
|
|
Limit := n;
|
|
l := n - Offset;
|
|
if l <= 0 then
|
|
exit;
|
|
if Limit > l then
|
|
Limit := l;
|
|
result := false; // not void
|
|
end;
|
|
|
|
procedure TDocVariantData.SetCount(aCount: integer);
|
|
begin
|
|
VCount := aCount;
|
|
end;
|
|
|
|
function TDocVariantData.Compare(const Another: TDocVariantData;
|
|
CaseInsensitive: boolean): integer;
|
|
var
|
|
n: integer;
|
|
ndx: PtrInt;
|
|
v1, v2: PVarData;
|
|
nameCmp: TDynArraySortCompare;
|
|
begin
|
|
// first validate the type: as { or [ in JSON
|
|
result := -1;
|
|
nameCmp := nil;
|
|
if IsArray then
|
|
begin
|
|
if not Another.IsArray then
|
|
exit;
|
|
end
|
|
else if IsObject then
|
|
if not Another.IsObject then
|
|
exit
|
|
else
|
|
nameCmp := SortDynArrayAnsiStringByCase[not IsCaseSensitive];
|
|
// compare as many in-order content as possible
|
|
n := Another.VCount;
|
|
if VCount < n then
|
|
n := VCount;
|
|
v1 := pointer(VValue);
|
|
v2 := pointer(Another.VValue);
|
|
for ndx := 0 to n - 1 do
|
|
begin
|
|
if Assigned(nameCmp) then
|
|
begin // each name should match
|
|
result := nameCmp(VName[ndx], Another.VName[ndx]);
|
|
if result <> 0 then
|
|
exit;
|
|
end;
|
|
result := FastVarDataComp(v1, v2, CaseInsensitive);
|
|
if result <> 0 then // each value should match
|
|
exit;
|
|
inc(v1);
|
|
inc(v2);
|
|
end;
|
|
// all content did match -> difference is now about the document count
|
|
result := VCount - Another.VCount;
|
|
end;
|
|
|
|
function TDocVariantData.CompareObject(const ObjFields: array of RawUtf8;
|
|
const Another: TDocVariantData; CaseInsensitive: boolean): integer;
|
|
var
|
|
f: PtrInt;
|
|
prev: integer;
|
|
v1, v2: PVariant;
|
|
begin
|
|
if IsObject then
|
|
if Another.IsObject then // compare Object, possibly by specified fields
|
|
begin
|
|
if high(ObjFields) < 0 then
|
|
begin
|
|
result := Compare(Another, CaseInsensitive);
|
|
exit;
|
|
end;
|
|
for f := 0 to high(ObjFields) do
|
|
begin
|
|
prev := -1; // optimistic: fields may be in the same position
|
|
GetObjectProp(ObjFields[f], v1, @prev);
|
|
Another.GetObjectProp(ObjFields[f], v2, @prev);
|
|
result := FastVarDataComp(pointer(v1), pointer(v2), CaseInsensitive);
|
|
if result <> 0 then // each value should match
|
|
exit;
|
|
end;
|
|
result := 0; // all supplied fields did match
|
|
end
|
|
else
|
|
result := 1 // Object, not Object
|
|
else if Another.IsObject then
|
|
result := -1 // not Object, Object
|
|
else
|
|
result := 0; // not Object, not Object
|
|
end;
|
|
|
|
function TDocVariantData.Equals(const Another: TDocVariantData;
|
|
CaseInsensitive: boolean): boolean;
|
|
begin
|
|
result := Compare(Another, CaseInsensitive) = 0;
|
|
end;
|
|
|
|
function TDocVariantData.Compare(const aName: RawUtf8; const aValue: variant;
|
|
aCaseInsensitive: boolean): integer;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
if (cardinal(VType) = DocVariantVType) and
|
|
GetObjectProp(aName, v{%H-}, nil) then
|
|
result := FastVarDataComp(pointer(v), @aValue, aCaseInsensitive)
|
|
else
|
|
result := -1;
|
|
end;
|
|
|
|
function TDocVariantData.Equals(const aName: RawUtf8; const aValue: variant;
|
|
aCaseInsensitive: boolean): boolean;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
result := (cardinal(VType) = DocVariantVType) and
|
|
GetObjectProp(aName, v{%H-}, nil) and
|
|
(FastVarDataComp(@aValue, pointer(v), aCaseInsensitive) = 0);
|
|
end;
|
|
|
|
function TDocVariantData.InternalAddBuf(aName: PUtf8Char; aNameLen: integer): integer;
|
|
var
|
|
tmp: RawUtf8; // so that the caller won't need to reserve such a temp var
|
|
begin
|
|
FastSetString(tmp, aName, aNameLen);
|
|
result := InternalAdd(tmp, -1);
|
|
end;
|
|
|
|
function TDocVariantData.InternalAdd(
|
|
const aName: RawUtf8; aIndex: integer): integer;
|
|
var
|
|
len: integer;
|
|
v: PVariantArray;
|
|
k: PRawUtf8Array;
|
|
begin
|
|
// validate consistent add/insert
|
|
if aName <> '' then
|
|
begin
|
|
if IsArray then
|
|
raise EDocVariant.CreateUtf8(
|
|
'Add: Unexpected [%] object property in an array', [aName]);
|
|
if not IsObject then
|
|
begin
|
|
VType := DocVariantVType; // may not be set yet
|
|
Include(dvoIsObject);
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
if IsObject then
|
|
raise EDocVariant.Create('Add: Unexpected array item in an object');
|
|
if not IsArray then
|
|
begin
|
|
VType := DocVariantVType; // may not be set yet
|
|
Include(dvoIsArray);
|
|
end;
|
|
end;
|
|
// grow up memory if needed
|
|
len := length(VValue);
|
|
if VCount >= len then
|
|
begin
|
|
len := NextGrow(VCount);
|
|
SetLength(VValue, len);
|
|
end;
|
|
result := VCount;
|
|
inc(VCount);
|
|
if cardinal(aIndex) < cardinal(result) then
|
|
begin
|
|
// reserve space for the inserted new item within existing data
|
|
dec(result, aIndex);
|
|
v := @VValue[aIndex];
|
|
MoveFast(v[0], v[1], result * SizeOf(variant));
|
|
PInteger(v)^ := varEmpty; // avoid GPF
|
|
if aName <> '' then
|
|
begin
|
|
if Length(VName) <> len then
|
|
SetLength(VName, len);
|
|
k := @VName[aIndex];
|
|
MoveFast(k[0], k[1], result * SizeOf(pointer));
|
|
PPointer(k)^ := nil; // avoid GPF
|
|
end;
|
|
result := aIndex;
|
|
end;
|
|
if aName = '' then
|
|
exit;
|
|
// store the object field name
|
|
if Length(VName) <> len then
|
|
SetLength(VName, len);
|
|
if Has(dvoInternNames) then
|
|
DocVariantType.InternNames.Unique(VName[result], aName)
|
|
else
|
|
VName[result] := aName;
|
|
end;
|
|
|
|
{$ifdef HASITERATORS}
|
|
|
|
function TDocVariantData.GetEnumerator: TDocVariantFieldsEnumerator;
|
|
begin
|
|
result.State.Init(pointer(Values), VCount);
|
|
if IsObject then
|
|
begin
|
|
result.Name := pointer(Names);
|
|
dec(result.Name);
|
|
end
|
|
else
|
|
result.Name := nil;
|
|
end;
|
|
|
|
function TDocVariantData.Items: TDocVariantItemsEnumerator;
|
|
begin
|
|
if IsObject then
|
|
result{%H-}.State.Void
|
|
else
|
|
result.State.Init(pointer(Values), VCount);
|
|
end;
|
|
|
|
function TDocVariantData.Objects: TDocVariantObjectsEnumerator;
|
|
begin
|
|
if IsObject then
|
|
result{%H-}.State.Void
|
|
else
|
|
result.State.Init(pointer(Values), VCount);
|
|
end;
|
|
|
|
function TDocVariantData.Fields: TDocVariantFieldsEnumerator;
|
|
begin
|
|
if IsArray then
|
|
result{%H-}.State.Void
|
|
else
|
|
result := GetEnumerator;
|
|
end;
|
|
|
|
function TDocVariantData.FieldNames: TDocVariantFieldNamesEnumerator;
|
|
begin
|
|
if IsArray or
|
|
(VCount = 0) then
|
|
begin
|
|
result.Curr := nil;
|
|
result.After := nil;
|
|
end
|
|
else
|
|
begin
|
|
result.Curr := pointer(Names);
|
|
result.After := @Names[VCount];
|
|
dec(result.Curr);
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.FieldValues: TDocVariantItemsEnumerator;
|
|
begin
|
|
if IsArray then
|
|
result{%H-}.State.Void
|
|
else
|
|
result.State.Init(pointer(Values), VCount);
|
|
end;
|
|
|
|
{$endif HASITERATORS}
|
|
|
|
procedure TDocVariantData.SetCapacity(aValue: integer);
|
|
begin
|
|
if IsObject then
|
|
SetLength(VName, aValue);
|
|
SetLength(VValue, aValue);
|
|
end;
|
|
|
|
function TDocVariantData.AddValue(const aName: RawUtf8; const aValue: variant;
|
|
aValueOwned: boolean; aIndex: integer): integer;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
if aName = '' then
|
|
begin
|
|
result := -1;
|
|
exit;
|
|
end;
|
|
if Has(dvoCheckForDuplicatedNames) then
|
|
if GetValueIndex(aName) >= 0 then
|
|
raise EDocVariant.CreateUtf8('AddValue: Duplicated [%] name', [aName]);
|
|
result := InternalAdd(aName, aIndex);
|
|
v := @VValue[result];
|
|
if aValueOwned then
|
|
v^ := aValue
|
|
else
|
|
SetVariantByValue(aValue, v^);
|
|
if Has(dvoInternValues) then
|
|
InternalUniqueValue(v);
|
|
end;
|
|
|
|
function TDocVariantData.AddValue(aName: PUtf8Char; aNameLen: integer;
|
|
const aValue: variant; aValueOwned: boolean; aIndex: integer): integer;
|
|
var
|
|
tmp: RawUtf8;
|
|
begin
|
|
FastSetString(tmp, aName, aNameLen);
|
|
result := AddValue(tmp, aValue, aValueOwned, aIndex);
|
|
end;
|
|
|
|
function TDocVariantData.AddValueFromText(const aName, aValue: RawUtf8;
|
|
DoUpdate: boolean): integer;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
if aName = '' then
|
|
begin
|
|
result := -1;
|
|
exit;
|
|
end;
|
|
result := GetValueIndex(aName);
|
|
if not DoUpdate and
|
|
(Has(dvoCheckForDuplicatedNames)) and
|
|
(result >= 0) then
|
|
raise EDocVariant.CreateUtf8(
|
|
'AddValueFromText: Duplicated [%] name', [aName]);
|
|
if result < 0 then
|
|
result := InternalAdd(aName);
|
|
v := @VValue[result];
|
|
VarClear(v^);
|
|
_FromText(VOptions, v, aValue); // recognize numbers
|
|
end;
|
|
|
|
procedure TDocVariantData.AddByPath(const aSource: TDocVariantData;
|
|
const aPaths: array of RawUtf8; aPathDelim: AnsiChar);
|
|
var
|
|
ndx, added: PtrInt;
|
|
v: TVarData;
|
|
begin
|
|
if (aSource.Count = 0) or
|
|
(not aSource.IsObject) or
|
|
IsArray then
|
|
exit;
|
|
for ndx := 0 to High(aPaths) do
|
|
begin
|
|
DocVariantType.Lookup(v, TVarData(aSource), pointer(aPaths[ndx]), aPathDelim);
|
|
if cardinal(v.VType) < varNull then
|
|
continue; // path not found
|
|
added := InternalAdd(aPaths[ndx]);
|
|
PVarData(@VValue[added])^ := v;
|
|
if Has(dvoInternValues) then
|
|
InternalUniqueValueAt(added);
|
|
end;
|
|
end;
|
|
|
|
procedure TDocVariantData.AddFrom(const aDocVariant: Variant);
|
|
var
|
|
src: PDocVariantData;
|
|
n: integer;
|
|
v: PVariant;
|
|
k: PRawUtf8;
|
|
begin
|
|
src := _Safe(aDocVariant);
|
|
n := src^.Count;
|
|
if n = 0 then
|
|
exit; // nothing to add
|
|
v := pointer(src^.VValue);
|
|
k := pointer(src^.VName);
|
|
if k = nil then // source aDocVariant is a dvArray
|
|
// add array items
|
|
if IsObject then
|
|
// types should match
|
|
exit
|
|
else
|
|
repeat
|
|
AddItem(v^);
|
|
inc(v);
|
|
dec(n)
|
|
until n = 0
|
|
else
|
|
// add object items
|
|
if IsArray then
|
|
// types should match
|
|
exit
|
|
else if Has(dvoCheckForDuplicatedNames) then
|
|
repeat
|
|
AddOrUpdateValue(k^, v^);
|
|
inc(k);
|
|
inc(v);
|
|
dec(n)
|
|
until n = 0
|
|
else
|
|
repeat
|
|
AddValue(k^, v^);
|
|
inc(k);
|
|
inc(v);
|
|
dec(n)
|
|
until n = 0;
|
|
end;
|
|
|
|
procedure TDocVariantData.AddOrUpdateFrom(const aDocVariant: Variant;
|
|
aOnlyAddMissing: boolean);
|
|
var
|
|
src: PDocVariantData;
|
|
n: integer;
|
|
v: PVariant;
|
|
k: PRawUtf8;
|
|
begin
|
|
src := _Safe(aDocVariant, dvObject);
|
|
n := src^.Count;
|
|
if n = 0 then
|
|
exit; // nothing to add
|
|
v := pointer(src^.VValue);
|
|
k := pointer(src^.VName);
|
|
repeat
|
|
AddOrUpdateValue(k^, v^, nil, aOnlyAddMissing);
|
|
inc(k);
|
|
inc(v);
|
|
dec(n)
|
|
until n = 0;
|
|
end;
|
|
|
|
function TDocVariantData.AddItem(const aValue: variant; aIndex: integer): integer;
|
|
begin
|
|
result := InternalAdd('', aIndex);
|
|
InternalSetValue(result, aValue);
|
|
end;
|
|
|
|
function TDocVariantData.AddItem(const aValue: TDocVariantData; aIndex: integer): integer;
|
|
begin
|
|
result := InternalAdd('', aIndex);
|
|
InternalSetValue(result, variant(aValue));
|
|
end;
|
|
|
|
function TDocVariantData.AddItemFromText(const aValue: RawUtf8; aIndex: integer): integer;
|
|
begin
|
|
result := InternalAdd('', aIndex);
|
|
_FromText(VOptions, @VValue[result], aValue); // recognize numbers
|
|
end;
|
|
|
|
function TDocVariantData.AddItemText(
|
|
const aValue: RawUtf8; aIndex: integer): integer;
|
|
begin
|
|
result := InternalAdd('', aIndex);
|
|
if Has(dvoInternValues) then
|
|
DocVariantType.InternValues.UniqueVariant(VValue[result], aValue)
|
|
else
|
|
RawUtf8ToVariant(aValue, VValue[result]);
|
|
end;
|
|
|
|
procedure TDocVariantData.AddItems(const aValue: array of const);
|
|
var
|
|
ndx, added: PtrInt;
|
|
begin
|
|
for ndx := 0 to high(aValue) do
|
|
begin
|
|
added := InternalAdd('');
|
|
VarRecToVariant(aValue[ndx], VValue[added]);
|
|
if Has(dvoInternValues) then
|
|
InternalUniqueValueAt(added);
|
|
end;
|
|
end;
|
|
|
|
procedure TDocVariantData.AddObject(const aNameValuePairs: array of const;
|
|
const aName: RawUtf8);
|
|
var
|
|
added: PtrInt;
|
|
obj: PDocVariantData;
|
|
begin
|
|
if (aName <> '') and
|
|
(Has(dvoCheckForDuplicatedNames)) then
|
|
if GetValueIndex(aName) >= 0 then
|
|
raise EDocVariant.CreateUtf8('AddObject: Duplicated [%] name', [aName]);
|
|
added := InternalAdd(aName);
|
|
obj := @VValue[added];
|
|
if PInteger(obj)^ = 0 then // most common case is adding a new value
|
|
obj^.InitClone(self) // same options than owner document
|
|
else if (obj^.VType <> VType) or
|
|
not obj^.IsObject then
|
|
raise EDocVariant.CreateUtf8('AddObject: wrong existing [%]', [aName]);
|
|
obj^.AddNameValuesToObject(aNameValuePairs);
|
|
if Has(dvoInternValues) then
|
|
InternalUniqueValueAt(added);
|
|
end;
|
|
|
|
function TDocVariantData.GetObjectProp(const aName: RawUtf8;
|
|
out aFound: PVariant; aPreviousIndex: PInteger): boolean;
|
|
var
|
|
ndx, n: PtrInt;
|
|
begin
|
|
result := false;
|
|
aFound := nil;
|
|
n := VCount;
|
|
if (n = 0) or
|
|
(aName = '') or
|
|
not IsObject then
|
|
exit;
|
|
ndx := -1;
|
|
if aPreviousIndex <> nil then
|
|
begin // optimistic try if this field is in the same place
|
|
ndx := aPreviousIndex^;
|
|
if (PtrUInt(ndx) >= PtrUInt(n)) or
|
|
(SortDynArrayAnsiStringByCase[not IsCaseSensitive](
|
|
VName[ndx], aName) <> 0) then
|
|
ndx := -1;
|
|
end;
|
|
if ndx < 0 then
|
|
ndx := FindNonVoid[IsCaseSensitive](
|
|
pointer(VName), pointer(aName), length(aName), n);
|
|
if ndx < 0 then
|
|
exit;
|
|
if aPreviousIndex <> nil then
|
|
aPreviousIndex^ := ndx;
|
|
aFound := @VValue[ndx];
|
|
result := true;
|
|
end;
|
|
|
|
function TDocVariantData.SearchItemByProp(const aPropName, aPropValue: RawUtf8;
|
|
aPropValueCaseSensitive: boolean): integer;
|
|
var
|
|
v: PVariant;
|
|
prev: integer;
|
|
begin
|
|
if IsObject then
|
|
begin
|
|
result := GetValueIndex(aPropName);
|
|
if (result >= 0) and
|
|
VariantEquals(VValue[result], aPropValue, aPropValueCaseSensitive) then
|
|
exit;
|
|
end
|
|
else if IsArray then
|
|
begin
|
|
prev := -1; // optimistic search aPropName at the previous field position
|
|
for result := 0 to VCount - 1 do
|
|
if _Safe(VValue[result])^.GetObjectProp(aPropName, v, @prev) and
|
|
VariantEquals({%H-}v^, aPropValue, aPropValueCaseSensitive) then
|
|
exit;
|
|
end;
|
|
result := -1;
|
|
end;
|
|
|
|
function TDocVariantData.SearchItemByProp(const aPropNameFmt: RawUtf8;
|
|
const aPropNameArgs: array of const; const aPropValue: RawUtf8;
|
|
aPropValueCaseSensitive: boolean): integer;
|
|
var
|
|
name: RawUtf8;
|
|
begin
|
|
FormatUtf8(aPropNameFmt, aPropNameArgs, name);
|
|
result := SearchItemByProp(name, aPropValue, aPropValueCaseSensitive);
|
|
end;
|
|
|
|
function TDocVariantData.SearchItemByValue(const aValue: Variant;
|
|
CaseInsensitive: boolean; StartIndex: PtrInt): PtrInt;
|
|
var
|
|
v: PVarData;
|
|
tmp: variant;
|
|
begin
|
|
SetVariantByValue(aValue, tmp); // ensure text is RawUtf8
|
|
v := @VValue[StartIndex];
|
|
for result := StartIndex to VCount - 1 do
|
|
if FastVarDataComp(v, @tmp, CaseInsensitive) = 0 then
|
|
exit
|
|
else
|
|
inc(v);
|
|
result := -1;
|
|
end;
|
|
|
|
function TDocVariantData.CountItemByValue(const aValue: Variant;
|
|
CaseInsensitive: boolean; StartIndex: integer): integer;
|
|
var
|
|
v: PVarData;
|
|
ndx: integer;
|
|
tmp: variant;
|
|
begin
|
|
result := 0; // returns the number of occurences of this value
|
|
SetVariantByValue(aValue, tmp); // ensure text is RawUtf8
|
|
v := @VValue[StartIndex];
|
|
for ndx := StartIndex to VCount - 1 do
|
|
begin
|
|
if FastVarDataComp(v, @tmp, CaseInsensitive) = 0 then
|
|
inc(result);
|
|
inc(v);
|
|
end;
|
|
end;
|
|
|
|
type
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TQuickSortDocVariant = record
|
|
{$else}
|
|
TQuickSortDocVariant = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
public
|
|
names: PPointerArray;
|
|
values: PVariantArray;
|
|
nameCompare: TUtf8Compare;
|
|
valueCompare: TVariantCompare;
|
|
valueComparer: TVariantComparer;
|
|
reversed: PtrInt;
|
|
procedure SortByName(L, R: PtrInt);
|
|
procedure SortByValue(L, R: PtrInt);
|
|
end;
|
|
|
|
procedure TQuickSortDocVariant.SortByName(L, R: PtrInt);
|
|
var
|
|
I, J, P: PtrInt;
|
|
pivot: pointer;
|
|
begin
|
|
if L < R then
|
|
repeat
|
|
I := L;
|
|
J := R;
|
|
P := (L + R) shr 1;
|
|
repeat
|
|
pivot := names[P];
|
|
while nameCompare(names[I], pivot) * reversed < 0 do
|
|
inc(I);
|
|
while nameCompare(names[J], pivot) * reversed > 0 do
|
|
dec(J);
|
|
if I <= J then
|
|
begin
|
|
if I <> J then
|
|
begin
|
|
ExchgPointer(@names[I], @names[J]);
|
|
ExchgVariant(@values[I], @values[J]);
|
|
end;
|
|
if P = I then
|
|
P := J
|
|
else if P = J then
|
|
P := I;
|
|
inc(I);
|
|
dec(J);
|
|
end;
|
|
until I > J;
|
|
if J - L < R - I then
|
|
begin
|
|
// use recursion only for smaller range
|
|
if L < J then
|
|
SortByName(L, J);
|
|
L := I;
|
|
end
|
|
else
|
|
begin
|
|
if I < R then
|
|
SortByName(I, R);
|
|
R := J;
|
|
end;
|
|
until L >= R;
|
|
end;
|
|
|
|
procedure TQuickSortDocVariant.SortByValue(L, R: PtrInt);
|
|
var
|
|
I, J, P: PtrInt;
|
|
pivot: PVariant;
|
|
begin
|
|
if L < R then
|
|
repeat
|
|
I := L;
|
|
J := R;
|
|
P := (L + R) shr 1;
|
|
repeat
|
|
pivot := @values[P];
|
|
if Assigned(valueCompare) then
|
|
begin // called from SortByValue
|
|
while valueCompare(values[I], pivot^) * reversed < 0 do
|
|
inc(I);
|
|
while valueCompare(values[J], pivot^) * reversed > 0 do
|
|
dec(J);
|
|
end
|
|
else
|
|
begin // called from SortByRow
|
|
while valueComparer(values[I], pivot^) * reversed < 0 do
|
|
inc(I);
|
|
while valueComparer(values[J], pivot^) * reversed > 0 do
|
|
dec(J);
|
|
end;
|
|
if I <= J then
|
|
begin
|
|
if I <> J then
|
|
begin
|
|
if names <> nil then
|
|
ExchgPointer(@names[I], @names[J]);
|
|
ExchgVariant(@values[I], @values[J]);
|
|
end;
|
|
if P = I then
|
|
P := J
|
|
else if P = J then
|
|
P := I;
|
|
inc(I);
|
|
dec(J);
|
|
end;
|
|
until I > J;
|
|
if J - L < R - I then
|
|
begin
|
|
// use recursion only for smaller range
|
|
if L < J then
|
|
SortByValue(L, J);
|
|
L := I;
|
|
end
|
|
else
|
|
begin
|
|
if I < R then
|
|
SortByValue(I, R);
|
|
R := J;
|
|
end;
|
|
until L >= R;
|
|
end;
|
|
|
|
procedure TDocVariantData.SortByName(
|
|
SortCompare: TUtf8Compare; SortCompareReversed: boolean);
|
|
var
|
|
qs: TQuickSortDocVariant;
|
|
begin
|
|
if (not IsObject) or
|
|
(VCount <= 1) then
|
|
exit;
|
|
if Assigned(SortCompare) then
|
|
qs.nameCompare := SortCompare
|
|
else
|
|
qs.nameCompare := @StrIComp;
|
|
qs.names := pointer(VName);
|
|
qs.values := pointer(VValue);
|
|
if SortCompareReversed then
|
|
qs.reversed := -1
|
|
else
|
|
qs.reversed := 1;
|
|
qs.SortByName(0, VCount - 1);
|
|
end;
|
|
|
|
procedure TDocVariantData.SortByValue(SortCompare: TVariantCompare;
|
|
SortCompareReversed: boolean);
|
|
var
|
|
qs: TQuickSortDocVariant;
|
|
begin
|
|
if VCount <= 1 then
|
|
exit;
|
|
if Assigned(SortCompare) then
|
|
qs.valueCompare := SortCompare
|
|
else
|
|
qs.valueCompare := @VariantCompare;
|
|
qs.valueComparer := nil;
|
|
qs.names := pointer(VName);
|
|
qs.values := pointer(VValue);
|
|
if SortCompareReversed then
|
|
qs.reversed := -1
|
|
else
|
|
qs.reversed := 1;
|
|
qs.SortByValue(0, VCount - 1);
|
|
end;
|
|
|
|
procedure TDocVariantData.SortByRow(const SortComparer: TVariantComparer;
|
|
SortComparerReversed: boolean);
|
|
var
|
|
qs: TQuickSortDocVariant;
|
|
begin
|
|
if (VCount <= 1) or
|
|
(not Assigned(SortComparer)) then
|
|
exit;
|
|
qs.valueCompare := nil;
|
|
qs.valueComparer := SortComparer;
|
|
qs.names := pointer(VName);
|
|
qs.values := pointer(VValue);
|
|
if SortComparerReversed then
|
|
qs.reversed := -1
|
|
else
|
|
qs.reversed := 1;
|
|
qs.SortByValue(0, VCount - 1);
|
|
end;
|
|
|
|
type
|
|
TQuickSortByFieldLookup = array[0..3] of PVariant;
|
|
PQuickSortByFieldLookup = ^TQuickSortByFieldLookup;
|
|
|
|
{$ifdef USERECORDWITHMETHODS}
|
|
TQuickSortDocVariantValuesByField = record
|
|
{$else}
|
|
TQuickSortDocVariantValuesByField = object
|
|
{$endif USERECORDWITHMETHODS}
|
|
public
|
|
Lookup: array of TQuickSortByFieldLookup; // per-name values
|
|
Compare: TVariantCompare;
|
|
CompareField: TVariantCompareField;
|
|
Fields: PRawUtf8Array;
|
|
P: PtrInt;
|
|
Pivot: PQuickSortByFieldLookup;
|
|
Doc: PDocVariantData;
|
|
TempExch: TQuickSortByFieldLookup;
|
|
Reverse: boolean;
|
|
Depth: integer; // = high(Lookup)
|
|
procedure Init(const aPropNames: array of RawUtf8;
|
|
aNameSortedCompare: TUtf8Compare);
|
|
function DoComp(Value: PQuickSortByFieldLookup): PtrInt;
|
|
{$ifndef CPUX86} inline; {$endif}
|
|
procedure Sort(L, R: PtrInt);
|
|
end;
|
|
|
|
procedure TQuickSortDocVariantValuesByField.Init(
|
|
const aPropNames: array of RawUtf8; aNameSortedCompare: TUtf8Compare);
|
|
var
|
|
namecomp: TUtf8Compare;
|
|
v: pointer;
|
|
row, f: PtrInt;
|
|
rowdata: PDocVariantData;
|
|
ndx: integer;
|
|
begin
|
|
Depth := high(aPropNames);
|
|
if (Depth < 0) or
|
|
(Depth > high(TQuickSortByFieldLookup)) then
|
|
raise EDocVariant.CreateUtf8('TDocVariantData.SortByFields(%)', [Depth]);
|
|
// resolve GetPVariantByName(aPropNames) once into Lookup[]
|
|
SetLength(Lookup, Doc^.VCount);
|
|
if Assigned(aNameSortedCompare) then // just like GetVarData() searches names
|
|
namecomp := aNameSortedCompare
|
|
else
|
|
namecomp := StrCompByCase[not Doc^.IsCaseSensitive];
|
|
for f := 0 to Depth do
|
|
begin
|
|
if aPropNames[f] = '' then
|
|
raise EDocVariant.CreateUtf8('TDocVariantData.SortByFields(%=void)', [f]);
|
|
ndx := -1;
|
|
for row := 0 to Doc^.VCount - 1 do
|
|
begin
|
|
rowdata := _Safe(Doc^.VValue[row]);
|
|
if (cardinal(ndx) < cardinal(rowdata^.VCount)) and
|
|
(namecomp(pointer(rowdata^.VName[ndx]), pointer(aPropNames[f])) = 0) then
|
|
v := @rowdata^.VValue[ndx] // get the value at the (likely) same position
|
|
else
|
|
begin
|
|
v := rowdata^.GetVarData(aPropNames[f], aNameSortedCompare, @ndx);
|
|
if v = nil then
|
|
v := @NullVarData;
|
|
end;
|
|
Lookup[row, f] := v;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TQuickSortDocVariantValuesByField.DoComp(
|
|
Value: PQuickSortByFieldLookup): PtrInt;
|
|
begin
|
|
if Assigned(Compare) then
|
|
begin
|
|
result := Compare(Value[0]^, Pivot[0]^);
|
|
if (result = 0) and
|
|
(depth > 0) then
|
|
begin
|
|
result := Compare(Value[1]^, Pivot[1]^);
|
|
if (result = 0) and
|
|
(depth > 1) then
|
|
begin
|
|
result := Compare(Value[2]^, Pivot[2]^);
|
|
if (result = 0) and
|
|
(depth > 2) then
|
|
result := Compare(Value[3]^, Pivot[3]^);
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
result := CompareField(Fields[0], Value[0]^, Pivot[0]^);
|
|
if (result = 0) and
|
|
(depth > 0) then
|
|
begin
|
|
result := CompareField(Fields[1], Value[1]^, Pivot[1]^);
|
|
if (result = 0) and
|
|
(depth > 1) then
|
|
begin
|
|
result := CompareField(Fields[2], Value[2]^, Pivot[2]^);
|
|
if (result = 0) and
|
|
(depth > 2) then
|
|
result := CompareField(Fields[3], Value[3]^, Pivot[3]^);
|
|
end;
|
|
end;
|
|
end;
|
|
if Reverse then
|
|
result := -result;
|
|
end;
|
|
|
|
procedure TQuickSortDocVariantValuesByField.Sort(L, R: PtrInt);
|
|
var
|
|
I, J: PtrInt;
|
|
begin
|
|
if L < R then
|
|
repeat
|
|
I := L;
|
|
J := R;
|
|
P := (L + R) shr 1;
|
|
repeat
|
|
Pivot := @Lookup[P];
|
|
while DoComp(@Lookup[I]) < 0 do
|
|
inc(I);
|
|
while DoComp(@Lookup[J]) > 0 do
|
|
dec(J);
|
|
if I <= J then
|
|
begin
|
|
if I <> J then
|
|
begin
|
|
if Doc.VName <> nil then
|
|
ExchgPointer(@Doc.VName[I], @Doc.VName[J]);
|
|
ExchgVariant(@Doc.VValue[I], @Doc.VValue[J]);
|
|
ExchgPointers(@Lookup[I], @Lookup[J], Depth + 1);
|
|
end;
|
|
if P = I then
|
|
P := J
|
|
else if P = J then
|
|
P := I;
|
|
inc(I);
|
|
dec(J);
|
|
end;
|
|
until I > J;
|
|
if J - L < R - I then
|
|
begin
|
|
// use recursion only for smaller range
|
|
if L < J then
|
|
Sort(L, J);
|
|
L := I;
|
|
end
|
|
else
|
|
begin
|
|
if I < R then
|
|
Sort(I, R);
|
|
R := J;
|
|
end;
|
|
until L >= R;
|
|
end;
|
|
|
|
procedure TDocVariantData.SortArrayByField(const aItemPropName: RawUtf8;
|
|
aValueCompare: TVariantCompare; aValueCompareReverse: boolean;
|
|
aNameSortedCompare: TUtf8Compare);
|
|
var
|
|
QS: TQuickSortDocVariantValuesByField;
|
|
begin
|
|
if (VCount <= 0) or
|
|
(aItemPropName = '') or
|
|
not IsArray then
|
|
exit;
|
|
if not Assigned(aValueCompare) then
|
|
aValueCompare := VariantCompare;
|
|
QS.Compare := aValueCompare;
|
|
QS.Doc := @self;
|
|
QS.Init([aItemPropName], aNameSortedCompare);
|
|
QS.Reverse := aValueCompareReverse;
|
|
QS.Sort(0, VCount - 1);
|
|
end;
|
|
|
|
procedure TDocVariantData.SortArrayByFields(
|
|
const aItemPropNames: array of RawUtf8; aValueCompare: TVariantCompare;
|
|
const aValueCompareField: TVariantCompareField;
|
|
aValueCompareReverse: boolean; aNameSortedCompare: TUtf8Compare);
|
|
var
|
|
QS: TQuickSortDocVariantValuesByField;
|
|
begin
|
|
if (VCount <= 0) or
|
|
not IsArray then
|
|
exit;
|
|
if Assigned(aValueCompareField) then
|
|
begin
|
|
QS.Compare := nil;
|
|
QS.Fields := @aItemPropNames[0];
|
|
QS.CompareField := aValueCompareField;
|
|
end
|
|
else if Assigned(aValueCompare) then
|
|
QS.Compare := aValueCompare
|
|
else
|
|
QS.Compare := VariantCompare;
|
|
QS.Doc := @self;
|
|
QS.Init(aItemPropNames, aNameSortedCompare);
|
|
QS.Reverse := aValueCompareReverse;
|
|
QS.Sort(0, VCount - 1);
|
|
end;
|
|
|
|
procedure TDocVariantData.Reverse;
|
|
begin
|
|
if VCount <= 0 then
|
|
exit;
|
|
if VName <> nil then
|
|
begin
|
|
DynArrayFakeLength(VName, VCount);
|
|
DynArray(TypeInfo(TRawUtf8DynArray), VName).Reverse;
|
|
end;
|
|
DynArrayFakeLength(VValue, VCount);
|
|
DynArray(TypeInfo(TVariantDynArray), VValue).Reverse;
|
|
end;
|
|
|
|
function TDocVariantData.Reduce(const aPropNames: array of RawUtf8;
|
|
aCaseSensitive, aDoNotAddVoidProp: boolean): variant;
|
|
begin
|
|
VarClear(result{%H-});
|
|
Reduce(aPropNames, aCaseSensitive, PDocVariantData(@result)^, aDoNotAddVoidProp);
|
|
end;
|
|
|
|
procedure TDocVariantData.Reduce(const aPropNames: array of RawUtf8;
|
|
aCaseSensitive: boolean; var result: TDocVariantData; aDoNotAddVoidProp: boolean);
|
|
var
|
|
ndx, j: PtrInt;
|
|
reduced: TDocVariantData;
|
|
begin
|
|
result.Init(VOptions); // same options than the main document
|
|
if (VCount = 0) or
|
|
(high(aPropNames) < 0) then
|
|
exit;
|
|
if IsObject then
|
|
for j := 0 to high(aPropNames) do
|
|
begin
|
|
if aPropNames[j] = '' then
|
|
continue; // avoid GPF in FindNonVoid()
|
|
ndx := FindNonVoid[aCaseSensitive](
|
|
pointer(VName), pointer(aPropNames[j]), length(aPropNames[j]), VCount);
|
|
if ndx >= 0 then
|
|
if not aDoNotAddVoidProp or
|
|
not VarIsVoid(VValue[ndx]) then
|
|
result.AddValue(VName[ndx], VValue[ndx]);
|
|
end
|
|
else if IsArray then
|
|
for ndx := 0 to VCount - 1 do
|
|
begin
|
|
_Safe(VValue[ndx])^.Reduce(
|
|
aPropNames, aCaseSensitive, reduced, aDoNotAddVoidProp);
|
|
if not reduced.IsObject then
|
|
continue;
|
|
result.AddItem(variant(reduced));
|
|
reduced.Clear;
|
|
end;
|
|
end;
|
|
|
|
procedure TDocVariantData.ReduceFilter(const aKey: RawUtf8;
|
|
const aValue: variant; aMatch: TCompareOperator; aCompare: TVariantCompare;
|
|
aLimit: integer; var result: TDocVariantData);
|
|
var
|
|
n, prev: integer;
|
|
v, obj: PVariant;
|
|
haspath: boolean;
|
|
dv: PDocVariantData;
|
|
begin
|
|
result.Init(VOptions, dvArray); // same options than the main document
|
|
n := VCount;
|
|
if (n = 0) or
|
|
(aKey = '') or
|
|
not IsArray then
|
|
exit;
|
|
if not Assigned(aCompare) then
|
|
aCompare := @VariantCompare;
|
|
prev := -1; // optimistic search aPropName at the previous field position
|
|
haspath := PosExChar('.', aKey) <> 0;
|
|
v := pointer(VValue);
|
|
repeat
|
|
dv := _Safe(v^);
|
|
if haspath then
|
|
obj := dv^.GetPVariantByPath(aKey, '.')
|
|
else
|
|
dv^.GetObjectProp(aKey, obj, @prev);
|
|
if (obj <> nil) and
|
|
SortMatch(aCompare({%H-}obj^, aValue), aMatch) then
|
|
begin
|
|
if result.VCount = 0 then
|
|
SetLength(result.VValue, n); // prepare for maximum capacity
|
|
result.AddItem(PVariant(dv)^);
|
|
end;
|
|
dec(aLimit);
|
|
if aLimit = 0 then
|
|
exit;
|
|
inc(v);
|
|
dec(n);
|
|
until n = 0;
|
|
end;
|
|
|
|
procedure TDocVariantData.ReduceFilter(const aExpression: RawUtf8;
|
|
var result: TDocVariantData; aLimit: integer; aCompare: TVariantCompare);
|
|
var
|
|
k: RawUtf8;
|
|
v: variant;
|
|
m: TCompareOperator;
|
|
begin
|
|
ParseSortMatch(pointer(aExpression), k, m, @v);
|
|
ReduceFilter(k, v, m, aCompare, aLimit, result);
|
|
end;
|
|
|
|
procedure ToSingle(result: PRttiVarData);
|
|
var
|
|
tmp: TDocVariantData;
|
|
begin
|
|
PRttiVarData(@tmp)^ := result^; // main dvArray to be finalized at exit
|
|
result^.VType := varEmpty;
|
|
if tmp.VCount <> 0 then
|
|
PVariant(result)^ := tmp.VValue[0]; // return the first (and unique) item
|
|
end;
|
|
|
|
function TDocVariantData.ReduceFilter(const aExpression: RawUtf8;
|
|
aLimit: integer): variant;
|
|
begin
|
|
VarClear(result{%H-});
|
|
ReduceFilter(aExpression, PDocVariantData(@result)^, aLimit);
|
|
if aLimit = 1 then
|
|
ToSingle(@result);
|
|
end;
|
|
|
|
procedure TDocVariantData.ReduceFilter(const aExpression: RawUtf8;
|
|
const aValue: variant; var result: TDocVariantData;
|
|
aCompare: TVariantCompare; aLimit: integer);
|
|
var
|
|
k: RawUtf8;
|
|
m: TCompareOperator;
|
|
begin
|
|
ParseSortMatch(pointer(aExpression), k, m, nil);
|
|
ReduceFilter(k, aValue, m, aCompare, aLimit, result);
|
|
end;
|
|
|
|
function TDocVariantData.ReduceFilter(const aExpression: RawUtf8;
|
|
const aValue: variant; aLimit: integer): variant;
|
|
begin
|
|
VarClear(result{%H-});
|
|
ReduceFilter(aExpression, aValue, PDocVariantData(@result)^);
|
|
if aLimit = 1 then
|
|
ToSingle(@result);
|
|
end;
|
|
|
|
function TDocVariantData.ReduceAsArray(const aPropName: RawUtf8;
|
|
const OnReduce: TOnReducePerItem): variant;
|
|
begin
|
|
VarClear(result{%H-});
|
|
ReduceAsArray(aPropName, PDocVariantData(@result)^, OnReduce);
|
|
end;
|
|
|
|
procedure TDocVariantData.ReduceAsArray(const aPropName: RawUtf8;
|
|
var result: TDocVariantData; const OnReduce: TOnReducePerItem);
|
|
var
|
|
ndx: PtrInt;
|
|
prev: integer;
|
|
item: PDocVariantData;
|
|
v: PVariant;
|
|
begin
|
|
result.Init(VOptions, dvArray); // same options than the main document
|
|
if (VCount = 0) or
|
|
(aPropName = '') or
|
|
not IsArray then
|
|
exit;
|
|
prev := -1; // optimistic search aPropName at the previous field position
|
|
for ndx := 0 to VCount - 1 do
|
|
if _Safe(VValue[ndx], item) and
|
|
{%H-}item^.GetObjectProp(aPropName, v, @prev) then
|
|
if (not Assigned(OnReduce)) or
|
|
OnReduce(item) then
|
|
result.AddItem(v^);
|
|
end;
|
|
|
|
function TDocVariantData.ReduceAsArray(const aPropName: RawUtf8;
|
|
const OnReduce: TOnReducePerValue): variant;
|
|
begin
|
|
VarClear(result{%H-});
|
|
ReduceAsArray(aPropName, PDocVariantData(@result)^, OnReduce);
|
|
end;
|
|
|
|
procedure TDocVariantData.ReduceAsArray(const aPropName: RawUtf8;
|
|
var result: TDocVariantData; const OnReduce: TOnReducePerValue);
|
|
var
|
|
ndx: PtrInt;
|
|
prev: integer;
|
|
v: PVariant;
|
|
begin
|
|
result.Init(VOptions, dvArray); // same options than the main document
|
|
if (VCount = 0) or
|
|
(aPropName = '') or
|
|
not IsArray then
|
|
exit;
|
|
prev := -1; // optimistic search aPropName at the previous field position
|
|
for ndx := 0 to VCount - 1 do
|
|
if _Safe(VValue[ndx])^.GetObjectProp(aPropName, v, @prev) then
|
|
if (not Assigned(OnReduce)) or
|
|
OnReduce(v^) then
|
|
result.AddItem(v^);
|
|
end;
|
|
|
|
function NotIn(a, v: PVarData; n: integer; caseins: boolean): boolean;
|
|
begin
|
|
result := false;
|
|
if n <> 0 then
|
|
repeat
|
|
if FastVarDataComp(a, v, caseins) = 0 then
|
|
exit;
|
|
inc(a);
|
|
dec(n);
|
|
until n = 0;
|
|
result := true;
|
|
end;
|
|
|
|
function TDocVariantData.ReduceAsVariantArray(const aPropName: RawUtf8;
|
|
aDuplicates: TSearchDuplicate): TVariantDynArray;
|
|
var
|
|
n, ndx: PtrInt;
|
|
prev: integer;
|
|
v: PVariant;
|
|
begin
|
|
result := nil;
|
|
if (VCount = 0) or
|
|
(aPropName = '') or
|
|
not IsArray then
|
|
exit;
|
|
prev := -1; // optimistic search aPropName at the previous field position
|
|
n := 0;
|
|
for ndx := 0 to VCount - 1 do
|
|
if _Safe(VValue[ndx])^.GetObjectProp(aPropName, v, @prev) then
|
|
if (aDuplicates = sdNone) or
|
|
NotIn(pointer(result), pointer(v), n, aDuplicates = sdCaseInsensitive) then
|
|
begin
|
|
if length(result) = n then
|
|
SetLength(result, NextGrow(n));
|
|
SetVariantByValue(PVariant(v)^, result[n]);
|
|
inc(n);
|
|
end;
|
|
if n <> 0 then
|
|
DynArrayFakeLength(result, n);
|
|
end;
|
|
|
|
function TDocVariantData.Rename(
|
|
const aFromPropName, aToPropName: TRawUtf8DynArray): integer;
|
|
var
|
|
n, prop, ndx: PtrInt;
|
|
begin
|
|
result := 0;
|
|
n := length(aFromPropName);
|
|
if length(aToPropName) = n then
|
|
for prop := 0 to n - 1 do
|
|
begin
|
|
ndx := GetValueIndex(aFromPropName[prop]);
|
|
if ndx >= 0 then
|
|
begin
|
|
VName[ndx] := aToPropName[prop];
|
|
inc(result);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.GetNames: TRawUtf8DynArray;
|
|
begin
|
|
if IsObject and
|
|
(VCount > 0) then
|
|
begin
|
|
DynArrayFakeLength(VName, VCount);
|
|
DynArrayFakeLength(VValue, VCount);
|
|
result := VName; // truncate with no memory (re)allocation
|
|
end
|
|
else
|
|
result := nil;
|
|
end;
|
|
|
|
function TDocVariantData.FlattenAsNestedObject(
|
|
const aObjectPropName: RawUtf8): boolean;
|
|
var
|
|
ndx, len: PtrInt;
|
|
Up: array[byte] of AnsiChar;
|
|
nested: TDocVariantData;
|
|
begin
|
|
// {"p.a1":5,"p.a2":"dfasdfa"} -> {"p":{"a1":5,"a2":"dfasdfa"}}
|
|
result := false;
|
|
if (VCount = 0) or
|
|
(aObjectPropName = '') or
|
|
(not IsObject) then
|
|
exit;
|
|
PWord(UpperCopy255(Up{%H-}, aObjectPropName))^ := ord('.'); // e.g. 'P.'
|
|
for ndx := 0 to Count - 1 do
|
|
if not IdemPChar(pointer(VName[ndx]), Up) then
|
|
exit; // all fields should match "p.####"
|
|
len := length(aObjectPropName) + 1;
|
|
for ndx := 0 to Count - 1 do
|
|
system.delete(VName[ndx], 1, len);
|
|
nested := self;
|
|
ClearFast;
|
|
InitObject([aObjectPropName, variant(nested)]);
|
|
result := true;
|
|
end;
|
|
|
|
function TDocVariantData.Delete(Index: PtrInt): boolean;
|
|
var
|
|
n: PtrInt;
|
|
v: PVariantArray;
|
|
k: PRawUtf8Array;
|
|
begin
|
|
result := cardinal(Index) < cardinal(VCount);
|
|
if not result then
|
|
exit;
|
|
dec(VCount);
|
|
if VCount = 0 then
|
|
begin
|
|
Void; // reset all in-memory storage and capacity
|
|
exit;
|
|
end;
|
|
k := pointer(VName);
|
|
if k <> nil then
|
|
begin
|
|
EnsureUnique(VName);
|
|
k := @VName[Index];
|
|
FastAssignNew(k[0]);
|
|
end;
|
|
EnsureUnique(VValue);
|
|
v := @VValue[Index];
|
|
VarClear(v[0]);
|
|
n := VCount - Index;
|
|
if n = 0 then
|
|
exit;
|
|
if k <> nil then
|
|
begin
|
|
MoveFast(k[1], k[0], n * SizeOf(pointer));
|
|
pointer(k[n]) := nil; // avoid GPF
|
|
end;
|
|
MoveFast(v[1], v[0], n * SizeOf(variant));
|
|
TRttiVarData(v[n]).VType := varEmpty; // avoid GPF
|
|
end;
|
|
|
|
function TDocVariantData.Extract(aIndex: integer; var aValue: variant;
|
|
aName: PRawUtf8): boolean;
|
|
var
|
|
v: pointer;
|
|
begin
|
|
result := false;
|
|
if aIndex < 0 then
|
|
inc(aIndex, VCount);
|
|
if cardinal(aIndex) >= cardinal(VCount) then
|
|
exit;
|
|
EnsureUnique(VValue);
|
|
VarClear(aValue);
|
|
v := @VValue[aIndex];
|
|
PRttiVarData(@aValue)^ := PRttiVarData(v)^; // no refcount
|
|
PRttiVarData(v)^.VType := varEmpty; // no VarClear(v^)
|
|
if aName <> nil then
|
|
if VName = nil then
|
|
FastAssignNew(aName^)
|
|
else
|
|
begin
|
|
EnsureUnique(VName);
|
|
v := @VName[aIndex];
|
|
FastAssignNew(aName^, PPointer(v)^); // no refcount
|
|
PPointer(v)^ := nil;
|
|
end;
|
|
result := Delete(aIndex);
|
|
end;
|
|
|
|
function TDocVariantData.Delete(const aName: RawUtf8; aValue: PVariant): boolean;
|
|
var
|
|
ndx: PtrInt;
|
|
begin
|
|
result := false;
|
|
ndx := GetValueIndex(aName);
|
|
if ndx >= 0 then
|
|
if aValue <> nil then
|
|
result := Extract(ndx, aValue^)
|
|
else
|
|
result := Delete(ndx);
|
|
end;
|
|
|
|
function TDocVariantData.Delete(const aNames: array of RawUtf8): integer;
|
|
var
|
|
n: PtrInt;
|
|
begin
|
|
result := 0;
|
|
for n := 0 to high(aNames) do
|
|
inc(result, ord(Delete(aNames[n])));
|
|
end;
|
|
|
|
function TDocVariantData.InternalNextPath(
|
|
var aCsv: PUtf8Char; aName: PShortString; aPathDelim: AnsiChar): PtrInt;
|
|
begin
|
|
GetNextItemShortString(aCsv, aName, aPathDelim);
|
|
if (VCount <> 0) and
|
|
(aName^[0] <> #0) then
|
|
if VName <> nil then
|
|
begin
|
|
result := FindNonVoid[IsCaseSensitive](
|
|
pointer(VName), @aName^[1], ord(aName^[0]), VCount);
|
|
exit;
|
|
end
|
|
else
|
|
begin
|
|
result := GetCardinalDef(@aName^[1], PtrUInt(-1));
|
|
if PtrUInt(result) < PtrUInt(VCount) then // array index integer as text
|
|
exit;
|
|
end;
|
|
result := -1;
|
|
end;
|
|
|
|
procedure TDocVariantData.InternalNotFound(var Dest: variant; aName: PUtf8Char);
|
|
begin
|
|
if Has(dvoReturnNullForUnknownProperty) then
|
|
SetVariantNull(Dest)
|
|
else
|
|
raise EDocVariant.CreateUtf8('[%] property not found', [aName])
|
|
end;
|
|
|
|
procedure TDocVariantData.InternalNotFound(var Dest: variant; aIndex: integer);
|
|
begin
|
|
if Has(dvoReturnNullForUnknownProperty) then
|
|
SetVariantNull(Dest)
|
|
else
|
|
raise EDocVariant.CreateUtf8('Out of range [%] (count=%)', [aIndex, VCount]);
|
|
end;
|
|
|
|
function TDocVariantData.InternalNotFound(aName: PUtf8Char): PVariant;
|
|
begin
|
|
if Has(dvoReturnNullForUnknownProperty) then
|
|
result := @DocVariantDataFake
|
|
else
|
|
raise EDocVariant.CreateUtf8('[%] property not found', [aName])
|
|
end;
|
|
|
|
function TDocVariantData.InternalNotFound(aIndex: integer): PDocVariantData;
|
|
begin
|
|
if Has(dvoReturnNullForUnknownProperty) then
|
|
result := @DocVariantDataFake
|
|
else
|
|
raise EDocVariant.CreateUtf8('Out of range [%] (count=%)', [aIndex, VCount]);
|
|
end;
|
|
|
|
function TDocVariantData.DeleteByPath(const aPath: RawUtf8;
|
|
aPathDelim: AnsiChar; aDeletedValue: PVariant): boolean;
|
|
var
|
|
csv: PUtf8Char;
|
|
dv: PDocVariantData;
|
|
ndx: PtrInt;
|
|
n: ShortString;
|
|
begin
|
|
result := false;
|
|
if IsArray then
|
|
exit;
|
|
csv := pointer(aPath);
|
|
dv := @self;
|
|
repeat
|
|
ndx := dv^.InternalNextPath(csv, @n, aPathDelim);
|
|
if csv = nil then
|
|
begin
|
|
// we reached the last item of the path, which is to be deleted
|
|
if aDeletedValue <> nil then
|
|
aDeletedValue^ := dv^.VValue[ndx];
|
|
result := dv^.Delete(ndx);
|
|
exit;
|
|
end;
|
|
until (ndx < 0) or
|
|
not _SafeObject(dv^.VValue[ndx], dv);
|
|
end;
|
|
|
|
function TDocVariantData.DeleteByProp(const aPropName, aPropValue: RawUtf8;
|
|
aPropValueCaseSensitive: boolean): boolean;
|
|
begin
|
|
result := Delete(SearchItemByProp(aPropName, aPropValue, aPropValueCaseSensitive));
|
|
end;
|
|
|
|
function TDocVariantData.DeleteByValue(const aValue: Variant;
|
|
CaseInsensitive: boolean): integer;
|
|
var
|
|
ndx: PtrInt;
|
|
begin
|
|
result := 0;
|
|
if VarIsEmptyOrNull(aValue) then
|
|
begin
|
|
for ndx := VCount - 1 downto 0 do
|
|
if VarDataIsEmptyOrNull(@VValue[ndx]) then
|
|
begin
|
|
Delete(ndx);
|
|
inc(result);
|
|
end;
|
|
end
|
|
else
|
|
for ndx := VCount - 1 downto 0 do
|
|
if FastVarDataComp(@VValue[ndx], @aValue, CaseInsensitive) = 0 then
|
|
begin
|
|
Delete(ndx);
|
|
inc(result);
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.DeleteByStartName(
|
|
aStartName: PUtf8Char; aStartNameLen: integer): integer;
|
|
var
|
|
ndx: PtrInt;
|
|
upname: array[byte] of AnsiChar;
|
|
begin
|
|
result := 0;
|
|
if aStartNameLen = 0 then
|
|
aStartNameLen := StrLen(aStartName);
|
|
if (VCount = 0) or
|
|
(not IsObject) or
|
|
(aStartNameLen = 0) then
|
|
exit;
|
|
UpperCopy255Buf(upname{%H-}, aStartName, aStartNameLen)^ := #0;
|
|
for ndx := Count - 1 downto 0 do
|
|
if IdemPChar(pointer(names[ndx]), upname) then
|
|
begin
|
|
Delete(ndx);
|
|
inc(result);
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.IsVoid: boolean;
|
|
begin
|
|
result := (cardinal(VType) <> DocVariantVType) or
|
|
(VCount = 0);
|
|
end;
|
|
|
|
function TDocVariantData.Exists(const aName: RawUtf8): boolean;
|
|
begin
|
|
result := GetValueIndex(Pointer(aName), Length(aName), IsCaseSensitive) >= 0;
|
|
end;
|
|
|
|
function TDocVariantData.GetValueIndex(aName: PUtf8Char; aNameLen: PtrInt;
|
|
aCaseSensitive: boolean): integer;
|
|
var
|
|
err: integer;
|
|
begin
|
|
if (cardinal(VType) = DocVariantVType) and
|
|
(aNameLen > 0) and
|
|
(aName <> nil) and
|
|
(VCount > 0) then
|
|
if IsArray then
|
|
begin
|
|
// try index integer as text, for lookup in array document
|
|
result := GetInteger(aName, err);
|
|
if (err <> 0) or
|
|
(cardinal(result) >= cardinal(VCount)) then
|
|
result := -1;
|
|
end
|
|
else
|
|
// O(n) lookup for name -> efficient brute force sub-functions
|
|
result := FindNonVoid[IsCaseSensitive](
|
|
pointer(VName), aName, aNameLen, VCount)
|
|
else
|
|
result := -1;
|
|
end;
|
|
|
|
function TDocVariantData.GetValueOrRaiseException(
|
|
const aName: RawUtf8): variant;
|
|
begin
|
|
RetrieveValueOrRaiseException(
|
|
pointer(aName), length(aName), IsCaseSensitive, result, false);
|
|
end;
|
|
|
|
function TDocVariantData.GetValueOrDefault(const aName: RawUtf8;
|
|
const aDefault: variant): variant;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
if (cardinal(VType) <> DocVariantVType) or
|
|
not GetObjectProp(aName, v{%H-}, nil) then
|
|
result := aDefault
|
|
else
|
|
SetVariantByValue(v^, result);
|
|
end;
|
|
|
|
function TDocVariantData.GetValueOrNull(const aName: RawUtf8): variant;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
if (cardinal(VType) <> DocVariantVType) or
|
|
not GetObjectProp(aName, v{%H-}, nil) then
|
|
SetVariantNull(result{%H-})
|
|
else
|
|
SetVariantByValue(v^, result);
|
|
end;
|
|
|
|
function TDocVariantData.GetValueOrEmpty(const aName: RawUtf8): variant;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
if (cardinal(VType) <> DocVariantVType) or
|
|
not GetObjectProp(aName, v{%H-}, nil) then
|
|
VarClear(result{%H-})
|
|
else
|
|
SetVariantByValue(v^, result);
|
|
end;
|
|
|
|
function TDocVariantData.GetAsBoolean(const aName: RawUtf8; out aValue: boolean;
|
|
aSortedCompare: TUtf8Compare): boolean;
|
|
var
|
|
found: PVarData;
|
|
begin
|
|
found := GetVarData(aName, aSortedCompare);
|
|
if found = nil then
|
|
result := false
|
|
else
|
|
result := VariantToBoolean(PVariant(found)^, aValue)
|
|
end;
|
|
|
|
function TDocVariantData.GetAsInteger(const aName: RawUtf8; out aValue: integer;
|
|
aSortedCompare: TUtf8Compare): boolean;
|
|
var
|
|
found: PVarData;
|
|
begin
|
|
found := GetVarData(aName, aSortedCompare);
|
|
if found = nil then
|
|
result := false
|
|
else
|
|
result := VariantToInteger(PVariant(found)^, aValue);
|
|
end;
|
|
|
|
function TDocVariantData.GetAsInt64(const aName: RawUtf8; out aValue: Int64;
|
|
aSortedCompare: TUtf8Compare): boolean;
|
|
var
|
|
found: PVarData;
|
|
begin
|
|
found := GetVarData(aName, aSortedCompare);
|
|
if found = nil then
|
|
result := false
|
|
else
|
|
result := VariantToInt64(PVariant(found)^, aValue)
|
|
end;
|
|
|
|
function TDocVariantData.GetAsDouble(const aName: RawUtf8; out aValue: double;
|
|
aSortedCompare: TUtf8Compare): boolean;
|
|
var
|
|
found: PVarData;
|
|
begin
|
|
found := GetVarData(aName, aSortedCompare);
|
|
if found = nil then
|
|
result := false
|
|
else
|
|
result := VariantToDouble(PVariant(found)^, aValue);
|
|
end;
|
|
|
|
function TDocVariantData.GetAsRawUtf8(const aName: RawUtf8; out aValue: RawUtf8;
|
|
aSortedCompare: TUtf8Compare): boolean;
|
|
var
|
|
found: PVarData;
|
|
wasString: boolean;
|
|
begin
|
|
found := GetVarData(aName, aSortedCompare);
|
|
if found = nil then
|
|
result := false
|
|
else
|
|
begin
|
|
if cardinal(found^.VType) > varNull then
|
|
// avoid default VariantToUtf8(null)='null'
|
|
VariantToUtf8(PVariant(found)^, aValue, wasString);
|
|
result := true;
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.GetValueEnumerate(const aName: RawUtf8;
|
|
aTypeInfo: PRttiInfo; out aValue; aDeleteFoundEntry: boolean): boolean;
|
|
var
|
|
text: RawUtf8;
|
|
ndx, ord: integer;
|
|
begin
|
|
result := false;
|
|
ndx := GetValueIndex(aName);
|
|
if (ndx < 0) or
|
|
not VariantToText(Values[ndx], text) then
|
|
exit;
|
|
ord := GetEnumNameValue(aTypeInfo, text, true);
|
|
if ord < 0 then
|
|
exit;
|
|
byte(aValue) := ord;
|
|
if aDeleteFoundEntry then
|
|
Delete(ndx);
|
|
result := true;
|
|
end;
|
|
|
|
function TDocVariantData.GetAsDocVariant(const aName: RawUtf8;
|
|
out aValue: PDocVariantData; aSortedCompare: TUtf8Compare): boolean;
|
|
var
|
|
found: PVarData;
|
|
begin
|
|
found := GetVarData(aName, aSortedCompare);
|
|
result := (found <> nil) and
|
|
_Safe(PVariant(found)^, aValue);
|
|
end;
|
|
|
|
function TDocVariantData.GetAsArray(const aName: RawUtf8;
|
|
out aArray: PDocVariantData; aSortedCompare: TUtf8Compare): boolean;
|
|
begin
|
|
result := GetAsDocVariant(aName, aArray, aSortedCompare) and
|
|
aArray^.IsArray and
|
|
(aArray^.Count > 0);
|
|
end;
|
|
|
|
function TDocVariantData.GetAsObject(const aName: RawUtf8;
|
|
out aObject: PDocVariantData; aSortedCompare: TUtf8Compare): boolean;
|
|
begin
|
|
result := GetAsDocVariant(aName, aObject, aSortedCompare) and
|
|
aObject^.IsObject and
|
|
(aObject^.Count > 0);
|
|
end;
|
|
|
|
function TDocVariantData.GetAsDocVariantSafe(const aName: RawUtf8;
|
|
aSortedCompare: TUtf8Compare): PDocVariantData;
|
|
var
|
|
found: PVarData;
|
|
begin
|
|
found := GetVarData(aName, aSortedCompare);
|
|
if found = nil then
|
|
result := @DocVariantDataFake
|
|
else
|
|
result := _Safe(PVariant(found)^);
|
|
end;
|
|
|
|
function TDocVariantData.GetAsPVariant(const aName: RawUtf8;
|
|
out aValue: PVariant; aSortedCompare: TUtf8Compare): boolean;
|
|
begin
|
|
aValue := pointer(GetVarData(aName, aSortedCompare));
|
|
result := aValue <> nil;
|
|
end;
|
|
|
|
function TDocVariantData.GetAsPVariant(
|
|
aName: PUtf8Char; aNameLen: PtrInt): PVariant;
|
|
var
|
|
ndx: PtrInt;
|
|
begin
|
|
ndx := GetValueIndex(aName, aNameLen, IsCaseSensitive);
|
|
if ndx >= 0 then
|
|
result := @VValue[ndx]
|
|
else
|
|
result := nil;
|
|
end;
|
|
|
|
function TDocVariantData.GetVarData(const aName: RawUtf8;
|
|
aSortedCompare: TUtf8Compare; aFoundIndex: PInteger): PVarData;
|
|
var
|
|
ndx: PtrInt;
|
|
begin
|
|
if (cardinal(VType) <> DocVariantVType) or
|
|
(not IsObject) or
|
|
(VCount = 0) or
|
|
(aName = '') then
|
|
begin
|
|
result := nil;
|
|
if aFoundIndex <> nil then
|
|
aFoundIndex^ := -1;
|
|
end
|
|
else
|
|
begin
|
|
if Assigned(aSortedCompare) then
|
|
if @aSortedCompare = @StrComp then
|
|
// use dedicated (branchless x86_64 asm) function for StrComp()
|
|
ndx := FastFindPUtf8CharSorted(pointer(VName), VCount - 1, pointer(aName))
|
|
else
|
|
ndx := FastFindPUtf8CharSorted(
|
|
pointer(VName), VCount - 1, pointer(aName), aSortedCompare)
|
|
else
|
|
ndx := FindNonVoid[IsCaseSensitive](
|
|
pointer(VName), pointer(aName), length(aName), VCount);
|
|
if aFoundIndex <> nil then
|
|
aFoundIndex^ := ndx;
|
|
if ndx >= 0 then
|
|
result := @VValue[ndx]
|
|
else
|
|
result := nil;
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.GetVarData(const aName: RawUtf8; var aValue: TVarData;
|
|
aSortedCompare: TUtf8Compare): boolean;
|
|
var
|
|
found: PVarData;
|
|
begin
|
|
found := GetVarData(aName, aSortedCompare);
|
|
if found = nil then
|
|
result := false
|
|
else
|
|
begin
|
|
aValue := found^;
|
|
result := true;
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.GetValueByPath(
|
|
const aPath: RawUtf8; aPathDelim: AnsiChar): variant;
|
|
var
|
|
Dest: TVarData;
|
|
begin
|
|
VarClear(result{%H-});
|
|
if (cardinal(VType) <> DocVariantVType) or
|
|
(VCount = 0) then
|
|
exit;
|
|
DocVariantType.Lookup(Dest, TVarData(self), pointer(aPath), aPathDelim);
|
|
if cardinal(Dest.VType) >= varNull then
|
|
result := variant(Dest); // copy
|
|
end;
|
|
|
|
function TDocVariantData.GetValueByPath(const aPath: RawUtf8;
|
|
out aValue: variant; aPathDelim: AnsiChar): boolean;
|
|
var
|
|
Dest: TVarData;
|
|
begin
|
|
result := false;
|
|
if (cardinal(VType) <> DocVariantVType) or
|
|
(VCount = 0) then
|
|
exit;
|
|
DocVariantType.Lookup(Dest, TVarData(self), pointer(aPath), aPathDelim);
|
|
if Dest.VType = varEmpty then
|
|
exit;
|
|
aValue := variant(Dest); // copy
|
|
result := true;
|
|
end;
|
|
|
|
function TDocVariantData.GetPVariantByPath(
|
|
const aPath: RawUtf8; aPathDelim: AnsiChar): PVariant;
|
|
var
|
|
ndx: PtrInt;
|
|
vt: cardinal;
|
|
csv: PUtf8Char;
|
|
n: ShortString;
|
|
begin
|
|
result := @self;
|
|
csv := pointer(aPath);
|
|
if aPath <> '' then
|
|
repeat
|
|
repeat
|
|
vt := PVarData(result)^.VType; // inlined dv := _Safe(result^)
|
|
if vt <> varVariantByRef then
|
|
break;
|
|
result := PVarData(result)^.VPointer;
|
|
until false;
|
|
if vt <> DocVariantVType then
|
|
break;
|
|
ndx := PDocVariantData(result)^.InternalNextPath(csv, @n, aPathDelim);
|
|
if ndx < 0 then
|
|
break;
|
|
result := @PDocVariantData(result)^.VValue[ndx];
|
|
if csv = nil then
|
|
exit; // exhausted all path, so result is the found item
|
|
until false;
|
|
result := nil;
|
|
end;
|
|
|
|
function TDocVariantData.GetPVariantExistingByPath(const aPath: RawUtf8;
|
|
aPathDelim: AnsiChar): PVariant;
|
|
begin
|
|
result := GetPVariantByPath(aPath, aPathDelim);
|
|
if result = nil then
|
|
result := InternalNotFound(pointer(aPath));
|
|
end;
|
|
|
|
function TDocVariantData.GetVariantByPath(const aNameOrPath: RawUtf8): Variant;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := GetPVariantByPath(aNameOrPath, '.');
|
|
if v <> nil then
|
|
SetVariantByValue(v^, result)
|
|
else
|
|
InternalNotFound(result, pointer(aNameOrPath));
|
|
end;
|
|
|
|
function TDocVariantData.GetDocVariantByPath(const aPath: RawUtf8;
|
|
out aValue: PDocVariantData; aPathDelim: AnsiChar): boolean;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := GetPVariantByPath(aPath, aPathDelim);
|
|
result := (v <> nil) and
|
|
_Safe(v^, aValue);
|
|
end;
|
|
|
|
function TDocVariantData.GetValueByPath(
|
|
const aDocVariantPath: array of RawUtf8): variant;
|
|
var
|
|
found, res: PVarData;
|
|
vt: cardinal;
|
|
ndx: integer;
|
|
begin
|
|
VarClear(result{%H-});
|
|
if (cardinal(VType) <> DocVariantVType) or
|
|
(not IsObject) or
|
|
(high(aDocVariantPath) < 0) then
|
|
exit;
|
|
found := @self;
|
|
ndx := 0;
|
|
repeat
|
|
found := PDocVariantData(found).GetVarData(aDocVariantPath[ndx]);
|
|
if found = nil then
|
|
exit;
|
|
if ndx = high(aDocVariantPath) then
|
|
break; // we found the item!
|
|
inc(ndx);
|
|
// if we reached here, we should try for the next scope within Dest
|
|
repeat
|
|
vt := found^.VType;
|
|
if vt <> varVariantByRef then
|
|
break;
|
|
found := found^.VPointer;
|
|
until false;
|
|
if vt = VType then
|
|
continue;
|
|
exit;
|
|
until false;
|
|
res := found;
|
|
while cardinal(res^.VType) = varVariantByRef do
|
|
res := res^.VPointer;
|
|
if (cardinal(res^.VType) = VType) and
|
|
(PDocVariantData(res)^.VCount = 0) then
|
|
// return void TDocVariant as null
|
|
TRttiVarData(result).VType := varNull
|
|
else
|
|
// copy found value
|
|
result := PVariant(found)^;
|
|
end;
|
|
|
|
function TDocVariantData.GetItemByProp(const aPropName, aPropValue: RawUtf8;
|
|
aPropValueCaseSensitive: boolean; var Dest: variant; DestByRef: boolean): boolean;
|
|
var
|
|
ndx: integer;
|
|
begin
|
|
result := false;
|
|
if not IsArray then
|
|
exit;
|
|
ndx := SearchItemByProp(aPropName, aPropValue, aPropValueCaseSensitive);
|
|
if ndx < 0 then
|
|
exit;
|
|
RetrieveValueOrRaiseException(ndx, Dest, DestByRef);
|
|
result := true;
|
|
end;
|
|
|
|
function TDocVariantData.GetDocVariantByProp(
|
|
const aPropName, aPropValue: RawUtf8; aPropValueCaseSensitive: boolean;
|
|
out Dest: PDocVariantData): boolean;
|
|
var
|
|
ndx: PtrInt;
|
|
begin
|
|
result := false;
|
|
if not IsArray then
|
|
exit;
|
|
ndx := SearchItemByProp(aPropName, aPropValue, aPropValueCaseSensitive);
|
|
if ndx >= 0 then
|
|
result := _Safe(VValue[ndx], Dest);
|
|
end;
|
|
|
|
function TDocVariantData.GetJsonByStartName(const aStartName: RawUtf8): RawUtf8;
|
|
var
|
|
Up: array[byte] of AnsiChar;
|
|
temp: TTextWriterStackBuffer;
|
|
n: integer;
|
|
nam: PPUtf8Char;
|
|
val: PVariant;
|
|
W: TJsonWriter;
|
|
begin
|
|
if (not IsObject) or
|
|
(VCount = 0) then
|
|
begin
|
|
result := NULL_STR_VAR;
|
|
exit;
|
|
end;
|
|
UpperCopy255(Up, aStartName)^ := #0;
|
|
W := TJsonWriter.CreateOwnedStream(temp);
|
|
try
|
|
W.Add('{');
|
|
n := VCount;
|
|
nam := pointer(VName);
|
|
val := pointer(VValue);
|
|
repeat
|
|
if IdemPChar(nam^, Up) then
|
|
begin
|
|
if Has(dvoSerializeAsExtendedJson) and
|
|
JsonPropNameValid(nam^) then
|
|
W.AddShort(nam^, PStrLen(nam^ - _STRLEN)^)
|
|
else
|
|
begin
|
|
W.AddDirect('"');
|
|
W.AddJsonEscape(nam^);
|
|
W.AddDirect('"');
|
|
end;
|
|
W.AddDirect(':');
|
|
W.AddVariant(val^, twJsonEscape);
|
|
W.AddComma;
|
|
end;
|
|
dec(n);
|
|
if n = 0 then
|
|
break;
|
|
inc(nam);
|
|
inc(val);
|
|
until false;
|
|
W.CancelLastComma('}');
|
|
W.SetText(result);
|
|
finally
|
|
W.Free;
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.GetValuesByStartName(const aStartName: RawUtf8;
|
|
TrimLeftStartName: boolean): variant;
|
|
var
|
|
Up: array[byte] of AnsiChar;
|
|
ndx: PtrInt;
|
|
name: RawUtf8;
|
|
begin
|
|
if aStartName = '' then
|
|
begin
|
|
result := Variant(self);
|
|
exit;
|
|
end;
|
|
if (not IsObject) or
|
|
(VCount = 0) then
|
|
begin
|
|
SetVariantNull(result{%H-});
|
|
exit;
|
|
end;
|
|
TDocVariant.NewFast(result);
|
|
UpperCopy255(Up{%H-}, aStartName)^ := #0;
|
|
for ndx := 0 to VCount - 1 do
|
|
if IdemPChar(Pointer(VName[ndx]), Up) then
|
|
begin
|
|
name := VName[ndx];
|
|
if TrimLeftStartName then
|
|
system.delete(name, 1, length(aStartName));
|
|
TDocVariantData(result).AddValue(name, VValue[ndx]);
|
|
end;
|
|
end;
|
|
|
|
procedure TDocVariantData.SetValueOrRaiseException(Index: integer;
|
|
const NewValue: variant);
|
|
begin
|
|
if cardinal(Index) >= cardinal(VCount) then
|
|
raise EDocVariant.CreateUtf8(
|
|
'Out of range Values[%] (count=%)', [Index, VCount])
|
|
else
|
|
VValue[Index] := NewValue;
|
|
end;
|
|
|
|
function TDocVariantData.SetValueByPath(const aPath: RawUtf8;
|
|
const aValue: variant; aCreateIfNotExisting: boolean; aPathDelim: AnsiChar): boolean;
|
|
var
|
|
csv: PUtf8Char;
|
|
v: PDocVariantData;
|
|
ndx: PtrInt;
|
|
n: ShortString;
|
|
begin
|
|
result := false;
|
|
if IsArray then
|
|
exit;
|
|
csv := pointer(aPath);
|
|
v := @self;
|
|
repeat
|
|
ndx := v^.InternalNextPath(csv, @n, aPathDelim);
|
|
if csv = nil then
|
|
break; // we reached the last item of the path, which is the value to set
|
|
if ndx < 0 then
|
|
if aCreateIfNotExisting then
|
|
begin
|
|
ndx := v^.InternalAddBuf(@n[1], ord(n[0])); // in two steps for FPC
|
|
v := @v^.VValue[ndx];
|
|
v^.InitClone(self); // same Options than root but with no Kind
|
|
end
|
|
else
|
|
exit
|
|
else if not _SafeObject(v^.VValue[ndx], v) then
|
|
exit; // incorrect path
|
|
until false;
|
|
if ndx < 0 then
|
|
ndx := v^.InternalAddBuf(@n[1], ord(n[0]));
|
|
v^.InternalSetValue(ndx, aValue);
|
|
result := true;
|
|
end;
|
|
|
|
procedure TDocVariantData.RetrieveNameOrRaiseException(
|
|
Index: integer; var Dest: RawUtf8);
|
|
begin
|
|
if (cardinal(Index) >= cardinal(VCount)) or
|
|
(VName = nil) then
|
|
if Has(dvoReturnNullForUnknownProperty) then
|
|
Dest := ''
|
|
else
|
|
raise EDocVariant.CreateUtf8(
|
|
'Out of range Names[%] (count=%)', [Index, VCount])
|
|
else
|
|
Dest := VName[Index];
|
|
end;
|
|
|
|
procedure TDocVariantData.RetrieveValueOrRaiseException(Index: integer;
|
|
var Dest: variant; DestByRef: boolean);
|
|
var
|
|
Source: PVariant;
|
|
begin
|
|
if cardinal(Index) >= cardinal(VCount) then
|
|
InternalNotFound(Dest, Index)
|
|
else if DestByRef then
|
|
SetVariantByRef(VValue[Index], Dest)
|
|
else
|
|
begin
|
|
Source := @VValue[Index];
|
|
while PVarData(Source)^.VType = varVariantByRef do
|
|
Source := PVarData(Source)^.VPointer;
|
|
Dest := Source^;
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.RetrieveValueOrRaiseException(
|
|
aName: PUtf8Char; aNameLen: integer; aCaseSensitive: boolean;
|
|
var Dest: variant; DestByRef: boolean): boolean;
|
|
var
|
|
ndx: integer;
|
|
begin
|
|
ndx := GetValueIndex(aName, aNameLen, aCaseSensitive);
|
|
if ndx < 0 then
|
|
InternalNotFound(Dest, aName)
|
|
else
|
|
RetrieveValueOrRaiseException(ndx, Dest, DestByRef);
|
|
result := ndx >= 0;
|
|
end;
|
|
|
|
function TDocVariantData.GetValueOrItem(const aNameOrIndex: variant): variant;
|
|
var
|
|
wasString: boolean;
|
|
Name: RawUtf8;
|
|
begin
|
|
if IsArray then
|
|
// fast index lookup e.g. for Value[1]
|
|
RetrieveValueOrRaiseException(
|
|
VariantToIntegerDef(aNameOrIndex, -1), result, true)
|
|
else
|
|
begin
|
|
// by name lookup e.g. for Value['abc']
|
|
VariantToUtf8(aNameOrIndex, Name, wasString);
|
|
if wasString then
|
|
RetrieveValueOrRaiseException(
|
|
pointer(Name), length(Name), IsCaseSensitive, result, true)
|
|
else
|
|
RetrieveValueOrRaiseException(
|
|
GetIntegerDef(pointer(Name), -1), result, true);
|
|
end;
|
|
end;
|
|
|
|
procedure TDocVariantData.SetValueOrItem(const aNameOrIndex, aValue: variant);
|
|
var
|
|
wasString: boolean;
|
|
ndx: integer;
|
|
Name: RawUtf8;
|
|
begin
|
|
if IsArray then
|
|
// fast index lookup e.g. for Value[1]
|
|
SetValueOrRaiseException(VariantToIntegerDef(aNameOrIndex, -1), aValue)
|
|
else
|
|
begin
|
|
// by name lookup e.g. for Value['abc']
|
|
VariantToUtf8(aNameOrIndex, Name, wasString);
|
|
if wasString then
|
|
begin
|
|
ndx := GetValueIndex(Name);
|
|
if ndx < 0 then
|
|
ndx := InternalAdd(Name);
|
|
InternalSetValue(ndx, aValue);
|
|
end
|
|
else
|
|
SetValueOrRaiseException(
|
|
VariantToIntegerDef(aNameOrIndex, -1), aValue);
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.AddOrUpdateValue(const aName: RawUtf8;
|
|
const aValue: variant; wasAdded: PBoolean; OnlyAddMissing: boolean): integer;
|
|
begin
|
|
if IsArray then
|
|
raise EDocVariant.CreateUtf8(
|
|
'AddOrUpdateValue("%") on an array', [aName]);
|
|
result := GetValueIndex(aName);
|
|
if result < 0 then
|
|
begin
|
|
result := InternalAdd(aName);
|
|
if wasAdded <> nil then
|
|
wasAdded^ := true;
|
|
end
|
|
else
|
|
begin
|
|
if wasAdded <> nil then
|
|
wasAdded^ := false;
|
|
if OnlyAddMissing then
|
|
exit;
|
|
end;
|
|
InternalSetValue(result, aValue);
|
|
end;
|
|
|
|
function TDocVariantData.ToJson: RawUtf8;
|
|
begin // note: FPC has troubles inlining this, but it is a slow method anyway
|
|
DocVariantType.ToJson(@self, result, '', '', jsonCompact);
|
|
end;
|
|
|
|
function TDocVariantData.ToJson(const Prefix, Suffix: RawUtf8;
|
|
Format: TTextWriterJsonFormat): RawUtf8;
|
|
begin
|
|
DocVariantType.ToJson(@self, result, Prefix, Suffix, Format);
|
|
end;
|
|
|
|
procedure TDocVariantData.SaveToJsonFile(const FileName: TFileName);
|
|
var
|
|
F: TStream;
|
|
W: TJsonWriter;
|
|
begin
|
|
if cardinal(VType) <> DocVariantVType then
|
|
exit;
|
|
F := TFileStreamEx.Create(FileName, fmCreate);
|
|
try
|
|
W := TJsonWriter.Create(F, 65536);
|
|
try
|
|
DocVariantType.ToJson(W, @self);
|
|
W.FlushFinal;
|
|
finally
|
|
W.Free;
|
|
end;
|
|
finally
|
|
F.Free;
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.ToNonExpandedJson: RawUtf8;
|
|
var
|
|
field: TRawUtf8DynArray;
|
|
fieldCount, r, f: PtrInt;
|
|
W: TJsonWriter;
|
|
row: PDocVariantData;
|
|
temp: TTextWriterStackBuffer;
|
|
begin
|
|
if not IsArray then
|
|
begin
|
|
result := '';
|
|
exit;
|
|
end;
|
|
if VCount = 0 then
|
|
begin
|
|
result := '[]';
|
|
exit;
|
|
end;
|
|
fieldCount := 0;
|
|
with _Safe(VValue[0])^ do
|
|
if IsObject then
|
|
begin
|
|
field := VName;
|
|
fieldCount := VCount;
|
|
end;
|
|
if fieldCount = 0 then
|
|
raise EDocVariant.Create('ToNonExpandedJson: Value[0] is not an object');
|
|
W := TJsonWriter.CreateOwnedStream(temp);
|
|
try
|
|
W.Add('{"fieldCount":%,"rowCount":%,"values":[', [fieldCount, VCount]);
|
|
for f := 0 to fieldCount - 1 do
|
|
begin
|
|
W.Add('"');
|
|
W.AddJsonEscape(pointer(field[f]));
|
|
W.Add('"', ',');
|
|
end;
|
|
for r := 0 to VCount - 1 do
|
|
begin
|
|
row := _Safe(VValue[r]);
|
|
if (r > 0) and
|
|
((not row^.IsObject) or
|
|
(row^.VCount <> fieldCount)) then
|
|
raise EDocVariant.CreateUtf8(
|
|
'ToNonExpandedJson: Value[%] not expected object', [r]);
|
|
for f := 0 to fieldCount - 1 do
|
|
if (r > 0) and
|
|
not PropNameEquals(row^.VName[f], field[f]) then
|
|
raise EDocVariant.CreateUtf8(
|
|
'ToNonExpandedJson: Value[%] field=% expected=%',
|
|
[r, row^.VName[f], field[f]])
|
|
else
|
|
begin
|
|
W.AddVariant(row^.VValue[f], twJsonEscape);
|
|
W.AddComma;
|
|
end;
|
|
end;
|
|
W.CancelLastComma;
|
|
W.AddDirect(']', '}');
|
|
W.SetText(result);
|
|
finally
|
|
W.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TDocVariantData.ToRawUtf8DynArray(out Result: TRawUtf8DynArray);
|
|
var
|
|
ndx: PtrInt;
|
|
wasString: boolean;
|
|
begin
|
|
if IsObject then
|
|
raise EDocVariant.Create('ToRawUtf8DynArray expects a dvArray');
|
|
if IsArray then
|
|
begin
|
|
SetLength(Result, VCount);
|
|
for ndx := 0 to VCount - 1 do
|
|
VariantToUtf8(VValue[ndx], Result[ndx], wasString);
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.ToRawUtf8DynArray: TRawUtf8DynArray;
|
|
begin
|
|
ToRawUtf8DynArray(result);
|
|
end;
|
|
|
|
function TDocVariantData.ToCsv(const Separator: RawUtf8): RawUtf8;
|
|
var
|
|
tmp: TRawUtf8DynArray; // fast enough in practice
|
|
begin
|
|
ToRawUtf8DynArray(tmp);
|
|
result := RawUtf8ArrayToCsv(tmp, Separator);
|
|
end;
|
|
|
|
procedure TDocVariantData.ToTextPairsVar(out Result: RawUtf8;
|
|
const NameValueSep, ItemSep: RawUtf8; escape: TTextWriterKind);
|
|
var
|
|
ndx: PtrInt;
|
|
temp: TTextWriterStackBuffer;
|
|
begin
|
|
if IsArray then
|
|
raise EDocVariant.Create('ToTextPairs expects a dvObject');
|
|
if (VCount > 0) and
|
|
IsObject then
|
|
with TJsonWriter.CreateOwnedStream(temp) do
|
|
try
|
|
ndx := 0;
|
|
repeat
|
|
AddString(VName[ndx]);
|
|
AddString(NameValueSep);
|
|
AddVariant(VValue[ndx], escape);
|
|
inc(ndx);
|
|
if ndx = VCount then
|
|
break;
|
|
AddString(ItemSep);
|
|
until false;
|
|
SetText(Result);
|
|
finally
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.ToTextPairs(const NameValueSep: RawUtf8;
|
|
const ItemSep: RawUtf8; Escape: TTextWriterKind): RawUtf8;
|
|
begin
|
|
ToTextPairsVar(result, NameValueSep, ItemSep, Escape);
|
|
end;
|
|
|
|
procedure TDocVariantData.ToArrayOfConst(out Result: TTVarRecDynArray);
|
|
begin
|
|
if IsObject then
|
|
raise EDocVariant.Create('ToArrayOfConst expects a dvArray');
|
|
if IsArray then
|
|
VariantsToArrayOfConst(VValue, VCount, Result);
|
|
end;
|
|
|
|
function TDocVariantData.ToArrayOfConst: TTVarRecDynArray;
|
|
begin
|
|
ToArrayOfConst(result);
|
|
end;
|
|
|
|
function TDocVariantData.ToUrlEncode(const UriRoot: RawUtf8): RawUtf8;
|
|
var
|
|
json: RawUtf8; // temporary in-place modified buffer
|
|
begin
|
|
DocVariantType.ToJson(@self, json);
|
|
result := UrlEncodeJsonObject(UriRoot, Pointer(json), []);
|
|
end;
|
|
|
|
function TDocVariantData.GetOrAddIndexByName(const aName: RawUtf8): integer;
|
|
begin
|
|
result := GetValueIndex(aName);
|
|
if result < 0 then
|
|
result := InternalAdd(aName);
|
|
end;
|
|
|
|
function TDocVariantData.GetOrAddPVariantByName(const aName: RawUtf8): PVariant;
|
|
var
|
|
ndx: PtrInt;
|
|
begin
|
|
ndx := GetOrAddIndexByName(aName); // in two steps for FPC
|
|
result := @VValue[ndx];
|
|
end;
|
|
|
|
function TDocVariantData.GetPVariantByName(const aName: RawUtf8): PVariant;
|
|
var
|
|
ndx: PtrInt;
|
|
begin
|
|
ndx := GetValueIndex(aName);
|
|
if ndx < 0 then
|
|
result := InternalNotFound(pointer(aName))
|
|
else
|
|
result := @VValue[ndx];
|
|
end;
|
|
|
|
function TDocVariantData.GetInt64ByName(const aName: RawUtf8): Int64;
|
|
begin
|
|
if not VariantToInt64(GetPVariantByName(aName)^, result) then
|
|
result := 0;
|
|
end;
|
|
|
|
function TDocVariantData.GetRawUtf8ByName(const aName: RawUtf8): RawUtf8;
|
|
var
|
|
wasString: boolean;
|
|
v: PVariant;
|
|
begin
|
|
v := GetPVariantByName(aName);
|
|
if PVarData(v)^.VType <= varNull then // default VariantToUtf8(null)='null'
|
|
result := ''
|
|
else
|
|
VariantToUtf8(v^, result, wasString);
|
|
end;
|
|
|
|
function TDocVariantData.GetStringByName(const aName: RawUtf8): string;
|
|
begin
|
|
result := VariantToString(GetPVariantByName(aName)^);
|
|
end;
|
|
|
|
procedure TDocVariantData.SetInt64ByName(const aName: RawUtf8;
|
|
const aValue: Int64);
|
|
begin
|
|
GetOrAddPVariantByName(aName)^ := aValue;
|
|
end;
|
|
|
|
procedure TDocVariantData.SetRawUtf8ByName(const aName, aValue: RawUtf8);
|
|
begin
|
|
RawUtf8ToVariant(aValue, GetOrAddPVariantByName(aName)^);
|
|
end;
|
|
|
|
procedure TDocVariantData.SetStringByName(const aName: RawUtf8;
|
|
const aValue: string);
|
|
begin
|
|
StringToVariant(aValue, GetOrAddPVariantByName(aName)^);
|
|
end;
|
|
|
|
function TDocVariantData.GetBooleanByName(const aName: RawUtf8): boolean;
|
|
begin
|
|
if not VariantToBoolean(GetPVariantByName(aName)^, result) then
|
|
result := false;
|
|
end;
|
|
|
|
procedure TDocVariantData.SetBooleanByName(const aName: RawUtf8;
|
|
aValue: boolean);
|
|
begin
|
|
GetOrAddPVariantByName(aName)^ := aValue;
|
|
end;
|
|
|
|
function TDocVariantData.GetDoubleByName(const aName: RawUtf8): Double;
|
|
begin
|
|
if not VariantToDouble(GetPVariantByName(aName)^, result) then
|
|
result := 0;
|
|
end;
|
|
|
|
procedure TDocVariantData.SetDoubleByName(const aName: RawUtf8;
|
|
const aValue: Double);
|
|
begin
|
|
GetOrAddPVariantByName(aName)^ := aValue;
|
|
end;
|
|
|
|
function TDocVariantData.GetDocVariantExistingByName(const aName: RawUtf8;
|
|
aNotMatchingKind: TDocVariantKind): PDocVariantData;
|
|
begin
|
|
result := GetAsDocVariantSafe(aName);
|
|
if result^.GetKind = aNotMatchingKind then
|
|
result := @DocVariantDataFake;
|
|
end;
|
|
|
|
function TDocVariantData.GetDocVariantOrAddByName(const aName: RawUtf8;
|
|
aKind: TDocVariantKind): PDocVariantData;
|
|
var
|
|
ndx: PtrInt;
|
|
begin
|
|
ndx := GetOrAddIndexByName(aName);
|
|
result := _Safe(VValue[ndx]);
|
|
if result^.Kind <> aKind then
|
|
begin
|
|
result := @VValue[ndx];
|
|
VarClear(PVariant(result)^);
|
|
result^.Init(VOptions, aKind);
|
|
end;
|
|
end;
|
|
|
|
function TDocVariantData.GetObjectExistingByName(
|
|
const aName: RawUtf8): PDocVariantData;
|
|
begin
|
|
result := GetDocVariantExistingByName(aName, dvArray);
|
|
end;
|
|
|
|
function TDocVariantData.GetObjectOrAddByName(
|
|
const aName: RawUtf8): PDocVariantData;
|
|
begin
|
|
result := GetDocVariantOrAddByName(aName, dvObject);
|
|
end;
|
|
|
|
function TDocVariantData.GetArrayExistingByName(
|
|
const aName: RawUtf8): PDocVariantData;
|
|
begin
|
|
result := GetDocVariantExistingByName(aName, dvObject);
|
|
end;
|
|
|
|
function TDocVariantData.GetArrayOrAddByName(
|
|
const aName: RawUtf8): PDocVariantData;
|
|
begin
|
|
result := GetDocVariantOrAddByName(aName, dvArray);
|
|
end;
|
|
|
|
function TDocVariantData.GetAsDocVariantByIndex(
|
|
aIndex: integer): PDocVariantData;
|
|
begin
|
|
if cardinal(aIndex) < cardinal(VCount) then
|
|
result := _Safe(VValue[aIndex])
|
|
else
|
|
result := InternalNotFound(aIndex);
|
|
end;
|
|
|
|
function _Obj(const NameValuePairs: array of const;
|
|
Options: TDocVariantOptions): variant;
|
|
begin
|
|
VarClear(result{%H-});
|
|
TDocVariantData(result).InitObject(NameValuePairs, Options);
|
|
end;
|
|
|
|
function _Arr(const Items: array of const;
|
|
Options: TDocVariantOptions): variant;
|
|
begin
|
|
VarClear(result{%H-});
|
|
TDocVariantData(result).InitArray(Items, Options);
|
|
end;
|
|
|
|
procedure _ObjAddProp(const Name: RawUtf8; const Value: variant;
|
|
var Obj: variant);
|
|
var
|
|
o: PDocVariantData;
|
|
begin
|
|
if _SafeObject(Obj, o) then
|
|
begin
|
|
// append new names/values to existing object
|
|
if o <> @Obj then
|
|
// ensure not stored by reference
|
|
TVarData(Obj) := PVarData(o)^;
|
|
o^.AddOrUpdateValue(Name, Value);
|
|
end
|
|
else
|
|
begin
|
|
// create new object
|
|
VarClear(Obj);
|
|
TDocVariantData(Obj).InitObject([Name, Value], JSON_FAST);
|
|
end
|
|
end;
|
|
|
|
procedure _ObjAddProp(const Name: RawUtf8; const Value: TDocVariantData;
|
|
var Obj: variant);
|
|
begin
|
|
_ObjAddProp(Name, variant(Value), Obj);
|
|
end;
|
|
|
|
procedure _ObjAddPropU(const Name: RawUtf8; const Value: RawUtf8;
|
|
var Obj: variant);
|
|
var
|
|
v: variant;
|
|
begin
|
|
RawUtf8ToVariant(Value, v);
|
|
_ObjAddProp(Name, v, Obj);
|
|
end;
|
|
|
|
procedure _ObjAddProps(const NameValuePairs: array of const;
|
|
var Obj: variant);
|
|
var
|
|
o: PDocVariantData;
|
|
begin
|
|
if _SafeObject(Obj, o) then
|
|
begin
|
|
// append new names/values to existing object
|
|
if o <> @Obj then
|
|
// ensure not stored by reference
|
|
TVarData(Obj) := PVarData(o)^;
|
|
o^.AddNameValuesToObject(NameValuePairs);
|
|
end
|
|
else
|
|
begin
|
|
// create new object
|
|
VarClear(Obj);
|
|
TDocVariantData(Obj).InitObject(NameValuePairs, JSON_FAST);
|
|
end
|
|
end;
|
|
|
|
procedure _ObjAddProps(const Document: variant; var Obj: variant);
|
|
var
|
|
ndx: PtrInt;
|
|
d, o: PDocVariantData;
|
|
begin
|
|
o := _Safe(Obj);
|
|
if _SafeObject(Document, d) then
|
|
if not o.IsObject then
|
|
Obj := Document
|
|
else
|
|
for ndx := 0 to d^.VCount - 1 do
|
|
o^.AddOrUpdateValue(d^.VName[ndx], d^.VValue[ndx]);
|
|
end;
|
|
|
|
function _ObjFast(const NameValuePairs: array of const): variant;
|
|
begin
|
|
VarClear(result{%H-});
|
|
TDocVariantData(result).InitObject(NameValuePairs, JSON_FAST);
|
|
end;
|
|
|
|
function _ObjFast(aObject: TObject;
|
|
aOptions: TTextWriterWriteObjectOptions): variant;
|
|
begin
|
|
ObjectToVariant(aObject, result, aOptions);
|
|
end;
|
|
|
|
function _ArrFast(const Items: array of const): variant;
|
|
begin
|
|
VarClear(result{%H-});
|
|
TDocVariantData(result).InitArray(Items, JSON_FAST);
|
|
end;
|
|
|
|
function _Json(const Json: RawUtf8; Options: TDocVariantOptions): variant;
|
|
begin
|
|
_Json(Json, result, Options);
|
|
end;
|
|
|
|
function _JsonFast(const Json: RawUtf8): variant;
|
|
begin
|
|
_Json(Json, result, JSON_FAST);
|
|
end;
|
|
|
|
function _JsonFastFloat(const Json: RawUtf8): variant;
|
|
begin
|
|
_Json(Json, result, JSON_FAST_FLOAT);
|
|
end;
|
|
|
|
function _JsonFastExt(const Json: RawUtf8): variant;
|
|
begin
|
|
_Json(Json, result, JSON_FAST_EXTENDED);
|
|
end;
|
|
|
|
function _JsonFmt(const Format: RawUtf8; const Args, Params: array of const;
|
|
Options: TDocVariantOptions): variant;
|
|
begin
|
|
_JsonFmt(Format, Args, Params, Options, result);
|
|
end;
|
|
|
|
procedure _JsonFmt(const Format: RawUtf8; const Args, Params: array of const;
|
|
Options: TDocVariantOptions; out Result: variant);
|
|
var
|
|
temp: RawUtf8;
|
|
begin
|
|
FormatParams(Format, Args, Params, {json=}true, temp);
|
|
if TDocVariantData(Result).InitJsonInPlace(pointer(temp), Options) = nil then
|
|
TDocVariantData(Result).ClearFast;
|
|
end;
|
|
|
|
function _JsonFastFmt(const Format: RawUtf8;
|
|
const Args, Params: array of const): variant;
|
|
begin
|
|
_JsonFmt(Format, Args, Params, JSON_FAST, result);
|
|
end;
|
|
|
|
function _Json(const Json: RawUtf8; var Value: variant;
|
|
Options: TDocVariantOptions): boolean;
|
|
begin
|
|
VarClear(Value);
|
|
if not TDocVariantData(Value).InitJson(Json, Options) then
|
|
begin
|
|
TDocVariantData(Value).ClearFast;
|
|
result := false;
|
|
end
|
|
else
|
|
result := true;
|
|
end;
|
|
|
|
procedure _Unique(var DocVariant: variant);
|
|
begin
|
|
// TDocVariantData(DocVariant): InitCopy() will check the DocVariant type
|
|
TDocVariantData(DocVariant).InitCopy(DocVariant, JSON_[mDefault]);
|
|
end;
|
|
|
|
procedure _UniqueFast(var DocVariant: variant);
|
|
begin
|
|
// TDocVariantData(DocVariant): InitCopy() will check the DocVariant type
|
|
TDocVariantData(DocVariant).InitCopy(DocVariant, JSON_[mFast]);
|
|
end;
|
|
|
|
function _Copy(const DocVariant: variant): variant;
|
|
begin
|
|
result := TDocVariant.NewUnique(DocVariant, JSON_[mDefault]);
|
|
end;
|
|
|
|
function _CopyFast(const DocVariant: variant): variant;
|
|
begin
|
|
result := TDocVariant.NewUnique(DocVariant, JSON_[mFast]);
|
|
end;
|
|
|
|
function _ByRef(const DocVariant: variant; Options: TDocVariantOptions): variant;
|
|
begin
|
|
VarClear(result{%H-});
|
|
TDocVariantData(result) := _Safe(DocVariant)^; // fast byref copy
|
|
TDocVariantData(result).SetOptions(Options);
|
|
end;
|
|
|
|
procedure _ByRef(const DocVariant: variant; out Dest: variant;
|
|
Options: TDocVariantOptions);
|
|
begin
|
|
TDocVariantData(Dest) := _Safe(DocVariant)^; // fast byref copy
|
|
TDocVariantData(Dest).SetOptions(Options);
|
|
end;
|
|
|
|
|
|
{ ************** JSON Parsing into Variant }
|
|
|
|
function GetVariantFromNotStringJson(Json: PUtf8Char; var Value: TVarData;
|
|
AllowDouble: boolean): boolean;
|
|
begin
|
|
if Json <> nil then
|
|
Json := GotoNextNotSpace(Json);
|
|
if (Json = nil) or
|
|
((PInteger(Json)^ = NULL_LOW) and
|
|
(jcEndOfJsonValueField in JSON_CHARS[Json[4]])) then
|
|
TRttiVarData(Value).VType := varNull
|
|
else if (PInteger(Json)^ = FALSE_LOW) and
|
|
(Json[4] = 'e') and
|
|
(jcEndOfJsonValueField in JSON_CHARS[Json[5]]) then
|
|
begin
|
|
TRttiVarData(Value).VType := varBoolean;
|
|
Value.VInteger := ord(false);
|
|
end
|
|
else if (PInteger(Json)^ = TRUE_LOW) and
|
|
(jcEndOfJsonValueField in JSON_CHARS[Json[4]]) then
|
|
begin
|
|
TRttiVarData(Value).VType := varBoolean;
|
|
Value.VInteger := ord(true);
|
|
end
|
|
else
|
|
begin
|
|
Json := GetNumericVariantFromJson(Json, Value, AllowDouble);
|
|
if (Json = nil) or
|
|
(GotoNextNotSpace(Json)^ <> #0) then
|
|
begin
|
|
result := false;
|
|
exit;
|
|
end;
|
|
end;
|
|
result := true;
|
|
end;
|
|
|
|
function GotoEndOfJsonNumber(P: PUtf8Char; var PEndNum: PUtf8Char): PUtf8Char;
|
|
{$ifdef HASINLINE} inline; {$endif} // inlined for better code generation
|
|
var
|
|
tab: PJsonCharSet;
|
|
begin
|
|
result := P;
|
|
tab := @JSON_CHARS;
|
|
repeat
|
|
inc(result);
|
|
until not (jcDigitFloatChar in tab[result^]);
|
|
PEndNum := result;
|
|
while not (jcEndOfJsonFieldNotName in tab[result^]) do
|
|
inc(result); // #0, ',', ']', '}'
|
|
end;
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
procedure GetJsonToAnyVariant(var Value: variant; var Json: PUtf8Char;
|
|
EndOfObject: PUtf8Char; Options: PDocVariantOptions; AllowDouble: boolean);
|
|
var
|
|
info: TGetJsonField;
|
|
begin
|
|
info.Json := Json;
|
|
JsonToAnyVariant(Value, Info, Options, AllowDouble);
|
|
if EndOfObject <> nil then
|
|
EndOfObject^ := info.EndOfObject;
|
|
Json := info.Json;
|
|
end;
|
|
{$endif PUREMORMOT2}
|
|
|
|
procedure JsonToAnyVariant(var Value: variant; var Info: TGetJsonField;
|
|
Options: PDocVariantOptions; AllowDouble: boolean);
|
|
var
|
|
V: TVarData absolute Value;
|
|
n: integer;
|
|
t: ^TSynInvokeableVariantType;
|
|
J, J2: PUtf8Char;
|
|
EndOfObject2: AnsiChar;
|
|
wasParsedWithinString: boolean;
|
|
label
|
|
parse, parsed, astext, endobj;
|
|
begin
|
|
if PInteger(@V)^ <> 0 then
|
|
VarClearProc(V);
|
|
if Info.Json = nil then
|
|
exit;
|
|
Info.EndOfObject := ' ';
|
|
if (Options <> nil) and
|
|
(dvoAllowDoubleValue in Options^) then
|
|
AllowDouble := true;
|
|
wasParsedWithinString := false;
|
|
J := Info.Json;
|
|
while (J^ <= ' ') and
|
|
(J^ <> #0) do
|
|
inc(J);
|
|
case JSON_TOKENS[J^] of
|
|
jtFirstDigit: // '-', '0'..'9': numbers are directly processed
|
|
begin
|
|
Info.Value := J;
|
|
J := GetNumericVariantFromJson(J, V, AllowDouble);
|
|
if J = nil then
|
|
begin
|
|
// not a supported number
|
|
if AllowDouble then
|
|
begin
|
|
Info.Json := nil; // we expected the precision to be enough
|
|
exit;
|
|
end;
|
|
// it may be a double value, but we didn't allow them -> store as text
|
|
J := Info.Value;
|
|
repeat
|
|
inc(J); // #0, ',', ']', '}'
|
|
until not (jcDigitFloatChar in JSON_CHARS[J^]);
|
|
Info.ValueLen := J - Info.Value;
|
|
J := GotoNextNotSpace(J);
|
|
Info.EndOfObject := J^;
|
|
if J^ <> #0 then
|
|
inc(J);
|
|
Info.Json := J;
|
|
goto astext;
|
|
end;
|
|
// we parsed a full number as variant
|
|
endobj: Info.ValueLen := J - Info.Value;
|
|
while (J^ <= ' ') and
|
|
(J^ <> #0) do
|
|
inc(J);
|
|
Info.EndOfObject := J^;
|
|
if J^ <> #0 then
|
|
inc(J);
|
|
Info.Json := J;
|
|
exit;
|
|
end;
|
|
jtDoubleQuote:
|
|
begin
|
|
Info.Json := J;
|
|
if (Options <> nil) and
|
|
(dvoJsonObjectParseWithinString in Options^) then
|
|
begin
|
|
Info.GetJsonField;
|
|
J := Info.Value;
|
|
wasParsedWithinString := true;
|
|
end
|
|
else
|
|
begin
|
|
// parse string/numerical values (or true/false/null constants)
|
|
parse: Info.GetJsonField;
|
|
parsed: if Info.WasString or
|
|
not GetVariantFromNotStringJson(Info.Value, V, AllowDouble) then
|
|
begin
|
|
astext: TRttiVarData(V).VType := varString;
|
|
V.VAny := nil; // avoid GPF below
|
|
FastSetString(RawUtf8(V.VAny), Info.Value, Info.Valuelen);
|
|
end;
|
|
exit;
|
|
end;
|
|
end;
|
|
jtNullFirstChar:
|
|
if (PInteger(J)^ = NULL_LOW) and
|
|
(jcEndOfJsonValueField in JSON_CHARS[J[4]]) then
|
|
begin
|
|
Info.Value := J;
|
|
TRttiVarData(V).VType := varNull;
|
|
inc(J, 4);
|
|
goto endobj;
|
|
end;
|
|
jtFalseFirstChar:
|
|
if (PInteger(J + 1)^ = FALSE_LOW2) and
|
|
(jcEndOfJsonValueField in JSON_CHARS[J[5]]) then
|
|
begin
|
|
Info.Value := J;
|
|
TRttiVarData(V).VType := varBoolean;
|
|
V.VInteger := ord(false);
|
|
inc(J, 5);
|
|
goto endobj;
|
|
end;
|
|
jtTrueFirstChar:
|
|
if (PInteger(J)^ = TRUE_LOW) and
|
|
(jcEndOfJsonValueField in JSON_CHARS[J[4]]) then
|
|
begin
|
|
Info.Value := J;
|
|
TRttiVarData(V).VType := varBoolean;
|
|
V.VInteger := ord(true);
|
|
inc(J, 4);
|
|
goto endobj;
|
|
end;
|
|
end;
|
|
// if we reach here, input Json may be some complex value
|
|
if Options = nil then
|
|
begin
|
|
Info.Json := nil;
|
|
exit; // clearly invalid basic JSON
|
|
end;
|
|
if not (dvoJsonParseDoNotTryCustomVariants in Options^) then
|
|
begin
|
|
// first call TryJsonToVariant() overriden method for any complex content
|
|
t := pointer(SynVariantTryJsonTypes);
|
|
if t <> nil then
|
|
begin
|
|
n := PDALen(PAnsiChar(t) - _DALEN)^ + _DAOFF; // call all TryJsonToVariant()
|
|
repeat
|
|
J2 := J;
|
|
// currently, only implemented by mormot.db.nosql.bson BsonVariantType
|
|
if t^.TryJsonToVariant(J2, Value, @EndOfObject2) then
|
|
begin
|
|
if not wasParsedWithinString then
|
|
begin
|
|
Info.EndOfObject := EndOfObject2;
|
|
Info.Json := J2;
|
|
end;
|
|
exit;
|
|
end;
|
|
dec(n);
|
|
if n = 0 then
|
|
break;
|
|
inc(t);
|
|
until false;
|
|
end;
|
|
end;
|
|
if J^ in ['{', '['] then
|
|
begin
|
|
// default Json parsing and conversion to TDocVariant instance
|
|
J := TDocVariantData(Value).InitJsonInPlace(J, Options^, @EndOfObject2);
|
|
if J = nil then
|
|
begin
|
|
TDocVariantData(Value).ClearFast;
|
|
Info.Json := nil;
|
|
exit; // error parsing
|
|
end;
|
|
if not wasParsedWithinString then
|
|
begin
|
|
Info.EndOfObject := EndOfObject2;
|
|
Info.Json := J;
|
|
end;
|
|
end
|
|
else // back to simple variant types
|
|
if wasParsedWithinString then
|
|
goto parsed
|
|
else
|
|
begin
|
|
Info.Json := J;
|
|
goto parse;
|
|
end;
|
|
end;
|
|
|
|
function TextToVariantNumberTypeNoDouble(Json: PUtf8Char): cardinal;
|
|
var
|
|
start: PUtf8Char;
|
|
c: AnsiChar;
|
|
begin
|
|
result := varString;
|
|
c := Json[0];
|
|
if (jcDigitFirstChar in JSON_CHARS[c]) and // ['-', '0'..'9']
|
|
(((c >= '1') and
|
|
(c <= '9')) or // is first char numeric?
|
|
((c = '0') and
|
|
((Json[1] = '.') or
|
|
(Json[1] = #0))) or // '012' is not Json, but '0.xx' and '0' are
|
|
((c = '-') and
|
|
(Json[1] >= '0') and
|
|
(Json[1] <= '9'))) then // negative number
|
|
begin
|
|
start := Json;
|
|
repeat
|
|
inc(Json)
|
|
until (Json^ < '0') or
|
|
(Json^ > '9'); // check digits
|
|
case Json^ of
|
|
#0:
|
|
if Json - start <= 19 then
|
|
// no decimal, and matcthing signed Int64 precision
|
|
result := varInt64;
|
|
'.':
|
|
if (Json[1] >= '0') and
|
|
(Json[1] <= '9') and
|
|
(Json[2] in [#0, '0'..'9']) then
|
|
if (Json[2] = #0) or
|
|
(Json[3] = #0) or
|
|
((Json[3] >= '0') and
|
|
(Json[3] <= '9') and
|
|
(Json[4] = #0) or
|
|
((Json[4] >= '0') and
|
|
(Json[4] <= '9') and
|
|
(Json[5] = #0))) then
|
|
result := varCurrency; // currency ###.1234 number
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TextToVariantNumberType(Json: PUtf8Char): cardinal;
|
|
var
|
|
start: PUtf8Char;
|
|
exp: PtrInt;
|
|
c: AnsiChar;
|
|
label
|
|
exponent;
|
|
begin
|
|
result := varString;
|
|
c := Json[0];
|
|
if (jcDigitFirstChar in JSON_CHARS[c]) and // ['-', '0'..'9']
|
|
(((c >= '1') and
|
|
(c <= '9')) or // is first char numeric?
|
|
((c = '0') and
|
|
((Json[1] = '.') or
|
|
(Json[1] = #0))) or // '012' is not Json, but '0.xx' and '0' are
|
|
((c = '-') and
|
|
(Json[1] >= '0') and
|
|
(Json[1] <= '9'))) then // negative number
|
|
begin
|
|
start := Json;
|
|
repeat
|
|
inc(Json)
|
|
until (Json^ < '0') or
|
|
(Json^ > '9'); // check digits
|
|
case Json^ of
|
|
#0:
|
|
if Json - start <= 19 then // signed Int64 precision
|
|
result := varInt64
|
|
else
|
|
result := varDouble; // we may loose precision, but still a number
|
|
'.':
|
|
if (Json[1] >= '0') and
|
|
(Json[1] <= '9') and
|
|
(Json[2] in [#0, '0'..'9']) then
|
|
if (Json[2] = #0) or
|
|
(Json[3] = #0) or
|
|
((Json[3] >= '0') and
|
|
(Json[3] <= '9') and
|
|
(Json[4] = #0) or
|
|
((Json[4] >= '0') and
|
|
(Json[4] <= '9') and
|
|
(Json[5] = #0))) then
|
|
result := varCurrency // currency ###.1234 number
|
|
else
|
|
begin
|
|
repeat // more than 4 decimals
|
|
inc(Json)
|
|
until (Json^ < '0') or
|
|
(Json^ > '9');
|
|
case Json^ of
|
|
#0:
|
|
result := varDouble;
|
|
'e',
|
|
'E':
|
|
begin
|
|
exponent: inc(Json); // inlined custom GetInteger()
|
|
start := Json;
|
|
c := Json^;
|
|
if (c = '-') or
|
|
(c = '+') then
|
|
begin
|
|
inc(Json);
|
|
c := Json^;
|
|
end;
|
|
inc(Json);
|
|
dec(c, 48);
|
|
if c > #9 then
|
|
exit;
|
|
exp := ord(c);
|
|
c := Json^;
|
|
dec(c, 48);
|
|
if c <= #9 then
|
|
begin
|
|
inc(Json);
|
|
exp := exp * 10 + ord(c);
|
|
c := Json^;
|
|
dec(c, 48);
|
|
if c <= #9 then
|
|
begin
|
|
inc(Json);
|
|
exp := exp * 10 + ord(c);
|
|
end;
|
|
end;
|
|
if Json^ <> #0 then
|
|
exit;
|
|
if start^ = '-' then
|
|
exp := -exp;
|
|
if (exp > -324) and
|
|
(exp < 308) then
|
|
result := varDouble; // 5.0 x 10^-324 .. 1.7 x 10^308
|
|
end;
|
|
end;
|
|
end;
|
|
'e',
|
|
'E':
|
|
goto exponent;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
const
|
|
CURRENCY_FACTOR: array[-4 .. -1] of integer = (1, 10, 100, 1000);
|
|
|
|
function GetNumericVariantFromJson(Json: PUtf8Char; var Value: TVarData;
|
|
AllowVarDouble: boolean): PUtf8Char;
|
|
var
|
|
// logic below is extracted from mormot.core.base.pas' GetExtended()
|
|
remdigit: integer;
|
|
frac, exp: PtrInt;
|
|
c: AnsiChar;
|
|
flags: set of (fNeg, fNegExp, fValid);
|
|
v64: Int64; // allows 64-bit resolution for the digits (match 80-bit extended)
|
|
d: double;
|
|
begin
|
|
// 1. parse input text as number into v64, frac, digit, exp
|
|
result := nil; // return nil to indicate parsing error
|
|
byte(flags) := 0;
|
|
v64 := 0;
|
|
frac := 0;
|
|
if Json = nil then
|
|
exit;
|
|
c := Json^;
|
|
if c = '-' then // note: '+xxx' is not valid Json so is not handled here
|
|
begin
|
|
c := Json[1];
|
|
inc(Json);
|
|
include(flags, fNeg);
|
|
end;
|
|
if (c = '0') and
|
|
(Json[1] >= '0') and
|
|
(Json[1] <= '9') then // '012' is not Json, but '0.xx' and '0' are
|
|
exit;
|
|
remdigit := 19; // max Int64 resolution
|
|
repeat
|
|
if (c >= '0') and
|
|
(c <= '9') then
|
|
begin
|
|
inc(Json);
|
|
dec(remdigit); // over-required digits are just ignored
|
|
if remdigit >= 0 then
|
|
begin
|
|
dec(c, ord('0'));
|
|
{$ifdef CPU64}
|
|
v64 := v64 * 10;
|
|
{$else}
|
|
v64 := v64 shl 3 + v64 + v64;
|
|
{$endif CPU64}
|
|
inc(v64, byte(c));
|
|
c := Json^;
|
|
include(flags, fValid);
|
|
if frac <> 0 then
|
|
dec(frac); // frac<0 for digits after '.'
|
|
continue;
|
|
end;
|
|
c := Json^;
|
|
if frac >= 0 then
|
|
inc(frac); // frac>0 to handle #############00000
|
|
continue;
|
|
end;
|
|
if c <> '.' then
|
|
break;
|
|
c := Json[1];
|
|
if (frac > 0) or
|
|
(c = #0) then // avoid ##.
|
|
exit;
|
|
inc(json);
|
|
dec(frac);
|
|
until false;
|
|
if frac < 0 then
|
|
inc(frac); // adjust digits after '.'
|
|
if (c = 'E') or
|
|
(c = 'e') then
|
|
begin
|
|
c := Json[1];
|
|
inc(Json);
|
|
exp := 0;
|
|
exclude(flags, fValid);
|
|
if c = '+' then
|
|
inc(Json)
|
|
else if c = '-' then
|
|
begin
|
|
inc(Json);
|
|
include(flags, fNegExp);
|
|
end;
|
|
repeat
|
|
c := Json^;
|
|
if (c < '0') or
|
|
(c > '9') then
|
|
break;
|
|
inc(Json);
|
|
dec(c, ord('0'));
|
|
exp := (exp * 10) + byte(c);
|
|
include(flags, fValid);
|
|
until false;
|
|
if fNegExp in flags then
|
|
dec(frac, exp)
|
|
else
|
|
inc(frac, exp);
|
|
end;
|
|
if not (fValid in flags) then
|
|
exit;
|
|
if fNeg in flags then
|
|
v64 := -v64;
|
|
// 2. now v64, frac, digit, exp contain number parsed from Json
|
|
if (frac = 0) and
|
|
(remdigit >= 0) then
|
|
begin
|
|
// return an integer or Int64 value
|
|
Value.VInt64 := v64;
|
|
if remdigit <= 9 then
|
|
TRttiVarData(Value).VType := varInt64
|
|
else
|
|
TRttiVarData(Value).VType := varInteger;
|
|
end
|
|
else if (frac < 0) and
|
|
(frac >= -4) then
|
|
begin
|
|
// currency as ###.0123
|
|
TRttiVarData(Value).VType := varCurrency;
|
|
Value.VInt64 := v64 * CURRENCY_FACTOR[frac]; // as round(CurrValue*10000)
|
|
end
|
|
else if AllowVarDouble and
|
|
(frac > -324) then // 5.0 x 10^-324 .. 1.7 x 10^308
|
|
begin
|
|
// converted into a double value
|
|
exp := PtrUInt(@POW10);
|
|
if frac >= -31 then
|
|
if frac <= 31 then
|
|
d := PPow10(exp)[frac] // -31 .. + 31
|
|
else if (18 - remdigit) + integer(frac) >= 308 then
|
|
exit // +308 ..
|
|
else
|
|
d := HugePower10Pos(frac, PPow10(exp)) // +32 .. +307
|
|
else
|
|
d := HugePower10Neg(frac, PPow10(exp)); // .. -32
|
|
Value.VDouble := d * v64;
|
|
TRttiVarData(Value).VType := varDouble;
|
|
end
|
|
else
|
|
exit;
|
|
result := Json; // returns the first char after the parsed number
|
|
end;
|
|
|
|
procedure UniqueVariant(Interning: TRawUtf8Interning; var aResult: variant;
|
|
aText: PUtf8Char; aTextLen: PtrInt; aAllowVarDouble: boolean);
|
|
var
|
|
tmp: RawUtf8;
|
|
begin
|
|
if not GetVariantFromNotStringJson(
|
|
aText, TVarData(aResult), aAllowVarDouble) then
|
|
begin
|
|
FastSetString(tmp, aText, aTextLen);
|
|
if Interning = nil then
|
|
RawUtf8ToVariant(tmp, aResult)
|
|
else
|
|
Interning.UniqueVariant(aResult, tmp);
|
|
end;
|
|
end;
|
|
|
|
procedure TextToVariant(const aValue: RawUtf8; AllowVarDouble: boolean;
|
|
out aDest: variant);
|
|
begin
|
|
try
|
|
if GetVariantFromNotStringJson(pointer(aValue), TVarData(aDest), AllowVarDouble) then
|
|
exit;
|
|
except // some obscure floating point exception may occur
|
|
end;
|
|
RawUtf8ToVariant(aValue, aDest);
|
|
end;
|
|
|
|
procedure TextBufferToVariant(aValue: PUtf8Char; AllowVarDouble: boolean;
|
|
out aDest: variant);
|
|
begin
|
|
try
|
|
if GetVariantFromNotStringJson(aValue, TVarData(aDest), AllowVarDouble) then
|
|
exit;
|
|
except // some obscure floating point exception may occur
|
|
end;
|
|
RawUtf8ToVariant(aValue, StrLen(aValue), aDest);
|
|
end;
|
|
|
|
function GetNextItemToVariant(var P: PUtf8Char; out Value: Variant;
|
|
Sep: AnsiChar; AllowDouble: boolean): boolean;
|
|
var
|
|
temp: RawUtf8;
|
|
begin
|
|
if P = nil then
|
|
result := false
|
|
else
|
|
begin
|
|
GetNextItem(P, Sep, temp);
|
|
TextToVariant(temp, AllowDouble, Value);
|
|
result := true;
|
|
end;
|
|
end;
|
|
|
|
procedure GetVariantFromJsonField(Json: PUtf8Char; wasString: boolean;
|
|
var Value: variant; TryCustomVariants: PDocVariantOptions;
|
|
AllowDouble: boolean; JsonLen: integer);
|
|
var
|
|
V: TVarData absolute Value;
|
|
info: TGetJsonField;
|
|
begin
|
|
// first handle any strict-Json syntax objects or arrays into custom variants
|
|
if (TryCustomVariants <> nil) and
|
|
(Json <> nil) then
|
|
if (GotoNextNotSpace(Json)^ in ['{', '[']) and
|
|
not wasString then
|
|
begin // also supports dvoJsonObjectParseWithinString
|
|
info.Json := Json;
|
|
JsonToAnyVariant(Value, info, TryCustomVariants, AllowDouble);
|
|
exit;
|
|
end
|
|
else if dvoAllowDoubleValue in TryCustomVariants^ then
|
|
AllowDouble := true;
|
|
// handle simple text or numerical values
|
|
VarClear(Value);
|
|
// try any numerical or true/false/null value
|
|
if wasString or
|
|
not GetVariantFromNotStringJson(Json, V, AllowDouble) then
|
|
begin
|
|
// found no numerical value -> return a string in the expected format
|
|
TRttiVarData(Value).VType := varString;
|
|
V.VString := nil; // avoid GPF below
|
|
if JsonLen = 0 then
|
|
JsonLen := StrLen(Json);
|
|
FastSetString(RawUtf8(V.VString), Json, JsonLen);
|
|
end;
|
|
end;
|
|
|
|
procedure _BinaryVariantLoadAsJson(var Value: variant; Json: PUtf8Char;
|
|
TryCustomVariant: pointer);
|
|
var
|
|
info: TGetJsonField;
|
|
begin
|
|
if TryCustomVariant = nil then
|
|
TryCustomVariant := @JSON_[mFast];
|
|
info.Json := Json;
|
|
JsonToAnyVariant(Value, info, TryCustomVariant, {double=}true);
|
|
end;
|
|
|
|
function VariantLoadJson(var Value: Variant; const Json: RawUtf8;
|
|
TryCustomVariants: PDocVariantOptions; AllowDouble: boolean): boolean;
|
|
var
|
|
tmp: TSynTempBuffer;
|
|
info: TGetJsonField;
|
|
begin
|
|
tmp.Init(Json); // temp copy before in-place decoding
|
|
try
|
|
info.Json := tmp.buf;
|
|
JsonToAnyVariant(Value, info, TryCustomVariants, AllowDouble);
|
|
result := info.Json <> nil;
|
|
finally
|
|
tmp.Done;
|
|
end;
|
|
end;
|
|
|
|
function VariantLoadJson(const Json: RawUtf8;
|
|
TryCustomVariants: PDocVariantOptions; AllowDouble: boolean): variant;
|
|
begin
|
|
VariantLoadJson(result, Json, TryCustomVariants, AllowDouble);
|
|
end;
|
|
|
|
function JsonToVariantInPlace(var Value: Variant; Json: PUtf8Char;
|
|
Options: TDocVariantOptions; AllowDouble: boolean): PUtf8Char;
|
|
var
|
|
info: TGetJsonField;
|
|
begin
|
|
info.Json := Json;
|
|
JsonToAnyVariant(Value, info, @Options, AllowDouble);
|
|
result := info.Json;
|
|
end;
|
|
|
|
function JsonToVariant(const Json: RawUtf8; Options: TDocVariantOptions;
|
|
AllowDouble: boolean): variant;
|
|
begin
|
|
VariantLoadJson(result, Json, @Options, AllowDouble);
|
|
end;
|
|
|
|
procedure MultiPartToDocVariant(const MultiPart: TMultiPartDynArray;
|
|
var Doc: TDocVariantData; Options: PDocVariantOptions);
|
|
var
|
|
ndx: PtrInt;
|
|
v: variant;
|
|
begin
|
|
if Options = nil then
|
|
Doc.InitFast(dvObject)
|
|
else
|
|
Doc.Init(Options^, dvObject);
|
|
for ndx := 0 to high(multipart) do
|
|
with MultiPart[ndx] do
|
|
if ContentType = TEXT_CONTENT_TYPE then
|
|
begin
|
|
// append as regular "Name":"TextValue" field
|
|
RawUtf8ToVariant(Content, v);
|
|
Doc.AddValue(name, v);
|
|
end
|
|
else
|
|
// append binary file as an object, with Base64-encoded data
|
|
Doc.AddValue(name, _ObjFast([
|
|
'data', BinToBase64(Content),
|
|
'filename', FileName,
|
|
'contenttype', ContentType]));
|
|
end;
|
|
|
|
function ParseSortMatch(Expression: PUtf8Char; out Key: RawUtf8;
|
|
out Match: TCompareOperator; Value: PVariant): boolean;
|
|
var
|
|
KB, KE, B: PUtf8Char;
|
|
begin
|
|
result := false;
|
|
if Expression = nil then
|
|
exit;
|
|
Expression := GotoNextNotSpace(Expression);
|
|
KB := Expression;
|
|
while jcJsonIdentifier in JSON_CHARS[Expression^] do
|
|
inc(Expression);
|
|
if Expression^ = #0 then
|
|
exit;
|
|
KE := Expression;
|
|
Expression := GotoNextNotSpace(Expression);
|
|
B := Expression;
|
|
while Expression^ in ['<', '>', '='] do
|
|
inc(Expression);
|
|
case Expression - B of
|
|
1:
|
|
case B^ of
|
|
'=':
|
|
Match := coEqualTo;
|
|
'<':
|
|
Match := coLessThan;
|
|
'>':
|
|
Match := coGreaterThan
|
|
else
|
|
exit;
|
|
end;
|
|
2:
|
|
case PWord(B)^ of
|
|
ord('=') + ord('=') shl 8: // c-style
|
|
Match := coEqualTo;
|
|
ord('!') + ord('=') shl 8, // c-style
|
|
ord('<') + ord('>') shl 8:
|
|
Match := coNotEqualTo;
|
|
ord('>') + ord('=') shl 8:
|
|
Match := coGreaterThanOrEqualTo;
|
|
ord('<') + ord('=') shl 8:
|
|
Match := coLessThanOrEqualTo;
|
|
else
|
|
exit;
|
|
end;
|
|
else
|
|
exit;
|
|
end;
|
|
FastSetString(Key, KB, KE - KB);
|
|
if Value <> nil then
|
|
TextBufferToVariant(GotoNextNotSpace(Expression), {allowdouble=}true, Value^);
|
|
result := true;
|
|
end;
|
|
|
|
{ ************** Variant Binary Serialization }
|
|
|
|
{$ifndef PUREMORMOT2}
|
|
|
|
function VariantSaveLength(const Value: variant): integer;
|
|
begin
|
|
result := {%H-}BinarySaveLength(@Value, TypeInfo(Variant), nil, [rkVariant]);
|
|
end;
|
|
|
|
function VariantSave(const Value: variant; Dest: PAnsiChar): PAnsiChar;
|
|
var
|
|
dummy: integer;
|
|
begin
|
|
result := {%H-}BinarySave(@Value, Dest, TypeInfo(Variant), dummy, [rkVariant]);
|
|
end;
|
|
|
|
{$endif PUREMORMOT2}
|
|
|
|
function VariantSave(const Value: variant): RawByteString;
|
|
begin
|
|
result := BinarySave(@Value, TypeInfo(Variant), [rkVariant]);
|
|
end;
|
|
|
|
function VariantLoad(var Value: variant; Source: PAnsiChar;
|
|
CustomVariantOptions: PDocVariantOptions; SourceMax: PAnsiChar): PAnsiChar;
|
|
begin
|
|
{$ifndef PUREMORMOT2}
|
|
if SourceMax = nil then
|
|
// mORMot 1 unsafe backward compatible: assume fake 100MB Source input
|
|
SourceMax := Source + 100 shl 20;
|
|
{$endif PUREMORMOT2}
|
|
result := BinaryLoad(@Value, Source, TypeInfo(Variant), nil, SourceMax,
|
|
[rkVariant], CustomVariantOptions);
|
|
end;
|
|
|
|
function VariantLoad(const Bin: RawByteString;
|
|
CustomVariantOptions: PDocVariantOptions): variant;
|
|
begin
|
|
BinaryLoad(@result, Bin, TypeInfo(Variant),
|
|
[rkVariant], CustomVariantOptions);
|
|
end;
|
|
|
|
procedure FromVarVariant(var Source: PByte; var Value: variant;
|
|
CustomVariantOptions: PDocVariantOptions; SourceMax: PByte);
|
|
begin
|
|
Source := PByte(VariantLoad(Value, pointer(Source),
|
|
CustomVariantOptions, pointer(SourceMax)));
|
|
end;
|
|
|
|
|
|
{ ************** IDocList/IDocDict advanced Wrappers of TDocVariant Documents }
|
|
|
|
type
|
|
TDocAny = class(TInterfacedSerializable)
|
|
protected
|
|
fValue: PDocVariantData;
|
|
fValueOwned: TVarData;
|
|
public
|
|
constructor CreateOwned;
|
|
constructor CreateNew(const dv: TDocVariantData; m: TDocVariantModel); reintroduce;
|
|
constructor CreateCopy(const dv: TDocVariantData); reintroduce;
|
|
constructor CreateByRef(dv: PDocVariantData); reintroduce;
|
|
destructor Destroy; override;
|
|
procedure OwnedAs(opt: PDocVariantOptions; added: TDocVariantOption);
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
procedure Clear;
|
|
function Kind: TDocVariantKind;
|
|
function Model: TDocVariantModel;
|
|
function Len: integer;
|
|
procedure ToJson(W: TJsonWriter; Options: TTextWriterWriteObjectOptions); override;
|
|
function Value: PDocVariantData;
|
|
function AsList: IDocList;
|
|
function AsDict: IDocDict;
|
|
function AsVariant: variant;
|
|
end;
|
|
|
|
TDocList = class(TDocAny, IDocList)
|
|
public
|
|
// TInterfacedSerializable methods
|
|
constructor Create(options: PDocVariantOptions); override;
|
|
procedure FromJson(var context: TJsonParserContext); override;
|
|
// IDocList methods
|
|
function GetB(position: integer): boolean;
|
|
function GetC(position: integer): currency;
|
|
function GetD(position: integer): IDocDict;
|
|
function GetF(position: integer): double;
|
|
function GetI(position: integer): Int64;
|
|
function GetItem(position: integer): variant;
|
|
function GetL(position: integer): IDocList;
|
|
function GetS(position: integer): string;
|
|
function GetU(position: integer): RawUtf8;
|
|
procedure SetB(position: integer; value: boolean);
|
|
procedure SetC(position: integer; const value: currency);
|
|
procedure SetD(position: integer; const value: IDocDict);
|
|
procedure SetF(position: integer; const value: double);
|
|
procedure SetI(position: integer; value: Int64);
|
|
procedure SetItem(position: integer; const value: variant);
|
|
procedure SetL(position: integer; const value: IDocList);
|
|
procedure SetS(position: integer; const value: string);
|
|
procedure SetU(position: integer; const value: RawUtf8);
|
|
//procedure SetJson(const value: RawUtf8); override;
|
|
function Append(const value: variant): integer; overload;
|
|
function Append(const value: RawUtf8): integer; overload;
|
|
function AppendDoc(const value: IDocAny): integer;
|
|
function Copy(start, stop: integer): IDocList;
|
|
function Compare(const another: IDocList; caseinsensitive: boolean): integer;
|
|
function Count(const value: variant): integer; overload;
|
|
function Count(const value: RawUtf8): integer; overload;
|
|
procedure Extend(const value: IDocList); overload;
|
|
procedure Extend(const value: array of const); overload;
|
|
function Filter(const key: RawUtf8; const value: variant; limit: integer;
|
|
match: TCompareOperator; compare: TVariantCompare): IDocList; overload;
|
|
function Filter(const expression: RawUtf8): IDocList; overload;
|
|
function Filter(const expression: RawUtf8; const value: variant;
|
|
limit: integer): IDocList; overload;
|
|
function First(const expression: RawUtf8): variant; overload;
|
|
function First(const expression: RawUtf8; const value: variant): variant; overload;
|
|
function Index(const value: variant): integer; overload;
|
|
function Index(const value: RawUtf8; caseinsensitive: boolean): integer; overload;
|
|
function Exists(const value: variant): boolean; overload;
|
|
function Exists(const value: RawUtf8; caseinsensitive: boolean): boolean; overload;
|
|
function Insert(position: integer; const value: variant): integer; overload;
|
|
function Insert(position: integer; const value: RawUtf8): integer; overload;
|
|
function ObjectsDictDynArray: IDocDictDynArray;
|
|
function Pop(position: integer): variant;
|
|
function PopItem(out value: variant; position: integer): boolean; overload;
|
|
function PopItem(out value: IDocDict; position: integer): boolean; overload;
|
|
function Del(position: integer): boolean;
|
|
function Reduce(const keys: array of RawUtf8): IDocList;
|
|
function Remove(const value: variant): integer; overload;
|
|
function Remove(const value: RawUtf8; caseinsensitive: boolean): integer; overload;
|
|
procedure Reverse;
|
|
procedure Sort(reverse: boolean; compare: TVariantCompare);
|
|
procedure SortByKeyValue(const key: RawUtf8; reverse: boolean;
|
|
compare: TVariantCompare); overload;
|
|
procedure SortByKeyValue(const keys: array of RawUtf8; reverse: boolean;
|
|
compare: TVariantCompare); overload;
|
|
function ValueAt(position: integer): PVariant;
|
|
{$ifdef HASIMPLICITOPERATOR}
|
|
function GetV(position: integer): TDocValue;
|
|
function GetEnumerator: TDocValueEnumerator;
|
|
function Range(start, stop: integer): TDocValueEnumerator;
|
|
function Objects: TDocObjectEnumerator; overload;
|
|
function Objects(const key: RawUtf8; const value: variant;
|
|
match: TCompareOperator; compare: TVariantCompare): TDocObjectEnumerator; overload;
|
|
function Objects(const expression: RawUtf8): TDocObjectEnumerator; overload;
|
|
function Objects(const expression: RawUtf8;
|
|
const value: variant): TDocObjectEnumerator; overload;
|
|
{$endif HASIMPLICITOPERATOR}
|
|
end;
|
|
|
|
TDocDict = class(TDocAny, IDocDict)
|
|
protected
|
|
fPathDelim: AnsiChar; // some additional parameters to this IDocDict state
|
|
fSorted: TUtf8Compare;
|
|
function GetValueAt(const key: RawUtf8; out value: PVariant): boolean;
|
|
function SetValueAt(const key: RawUtf8; const value: variant): boolean;
|
|
function GetExistingValueAt(const key, method: RawUtf8): PVariant;
|
|
function PopAt(const key: RawUtf8; value: PVariant): boolean;
|
|
public
|
|
// TInterfacedSerializable methods
|
|
constructor Create(options: PDocVariantOptions); override;
|
|
procedure FromJson(var context: TJsonParserContext); override;
|
|
// IDocDict methods
|
|
function GetB(const key: RawUtf8): boolean;
|
|
function GetC(const key: RawUtf8): currency;
|
|
function GetD(const key: RawUtf8): IDocDict;
|
|
function GetF(const key: RawUtf8): double;
|
|
function GetI(const key: RawUtf8): Int64;
|
|
function GetItem(const key: RawUtf8): variant;
|
|
function GetL(const key: RawUtf8): IDocList;
|
|
function GetS(const key: RawUtf8): string;
|
|
function GetU(const key: RawUtf8): RawUtf8;
|
|
procedure SetB(const key: RawUtf8; const value: boolean);
|
|
procedure SetC(const key: RawUtf8; const value: currency);
|
|
procedure SetD(const key: RawUtf8; const value: IDocDict);
|
|
procedure SetF(const key: RawUtf8; const value: double);
|
|
procedure SetI(const key: RawUtf8; const value: Int64);
|
|
procedure SetItem(const key: RawUtf8; const value: variant);
|
|
procedure SetL(const key: RawUtf8; const value: IDocList);
|
|
procedure SetS(const key: RawUtf8; const value: string);
|
|
procedure SetU(const key: RawUtf8; const value: RawUtf8);
|
|
function Get(const key: RawUtf8): variant; overload;
|
|
function GetDef(const key: RawUtf8; const default: variant): variant; overload;
|
|
function GetDef(const key: RawUtf8; const default: RawUtf8): variant; overload;
|
|
function Get(const key: RawUtf8; var value: variant): boolean; overload;
|
|
function Get(const key: RawUtf8; var value: RawUtf8): boolean; overload;
|
|
function Get(const key: RawUtf8; var value: string): boolean; overload;
|
|
function Get(const key: RawUtf8; var value: boolean): boolean; overload;
|
|
function Get(const key: RawUtf8; var value: integer): boolean; overload;
|
|
function Get(const key: RawUtf8; var value: Int64): boolean; overload;
|
|
function Get(const key: RawUtf8; var value: double): boolean; overload;
|
|
function Get(const key: RawUtf8; var value: currency): boolean; overload;
|
|
function Get(const key: RawUtf8; var value: IDocList): boolean; overload;
|
|
function Get(const key: RawUtf8; var value: IDocDict): boolean; overload;
|
|
function Get(const key: RawUtf8; var value: PDocVariantData): boolean; overload;
|
|
function GetPathDelim: AnsiChar;
|
|
procedure SetPathDelim(value: AnsiChar);
|
|
function Compare(const another: IDocDict; caseinsensitive: boolean): integer; overload;
|
|
function Compare(const another: IDocDict; const keys: array of RawUtf8;
|
|
caseinsensitive: boolean = false): integer; overload;
|
|
function Copy: IDocDict;
|
|
function Del(const key: RawUtf8): boolean;
|
|
function Exists(const key: RawUtf8): boolean;
|
|
function Pop(const key: RawUtf8): variant; overload;
|
|
function Pop(const key: RawUtf8; const default: variant): variant; overload;
|
|
function PopItem(out key: RawUtf8; out value: variant; position: integer): boolean;
|
|
function Reduce(const keys: array of RawUtf8): IDocDict;
|
|
function SetDefault(const key: RawUtf8): variant; overload;
|
|
function SetDefault(const key: RawUtf8; const default: variant): variant; overload;
|
|
procedure Sort(reverse: boolean; keycompare: TUtf8Compare);
|
|
procedure Update(const key: RawUtf8; const value: variant); overload;
|
|
procedure Update(const keyvalues: array of const); overload;
|
|
procedure Update(const source: IDocDict; addonlymissing: boolean); overload;
|
|
function ValueAt(const key: RawUtf8): PVariant;
|
|
{$ifdef HASIMPLICITOPERATOR}
|
|
function GetV(const key: RawUtf8): TDocValue;
|
|
function GetEnumerator: TDocDictEnumerator;
|
|
function Keys: TDocKeyEnumerator;
|
|
function Values: TDocValueEnumerator;
|
|
{$endif HASIMPLICITOPERATOR}
|
|
end;
|
|
|
|
{$ifdef HASIMPLICITOPERATOR}
|
|
|
|
{ TDocValue }
|
|
|
|
function TDocValue.IsString: boolean;
|
|
begin
|
|
result := VarIsStr(V^);
|
|
end;
|
|
|
|
function TDocValue.Kind: TDocVariantKind;
|
|
begin
|
|
result := _Safe(V^).GetKind;
|
|
end;
|
|
|
|
class operator TDocValue.Implicit(const A: TDocValue): boolean;
|
|
begin
|
|
if not VariantToBoolean(A.V^, result) then
|
|
result := false;
|
|
end;
|
|
|
|
class operator TDocValue.Implicit(const A: TDocValue): integer;
|
|
begin
|
|
if not VariantToInteger(A.V^, result) then
|
|
result := 0;
|
|
end;
|
|
|
|
class operator TDocValue.Implicit(const A: TDocValue): Int64;
|
|
begin
|
|
if not VariantToInt64(A.V^, result) then
|
|
result := 0;
|
|
end;
|
|
|
|
class operator TDocValue.Implicit(const A: TDocValue): string;
|
|
begin
|
|
VariantToString(A.V^, result);
|
|
end;
|
|
|
|
class operator TDocValue.Implicit(const A: TDocValue): RawUtf8;
|
|
var
|
|
wasString: boolean;
|
|
begin
|
|
VariantToUtf8(A.V^, RawUtf8(result), wasString);
|
|
end;
|
|
|
|
class operator TDocValue.Implicit(const A: TDocValue): IDocList;
|
|
begin
|
|
result := DocListFrom(A.V^);
|
|
end;
|
|
|
|
class operator TDocValue.Implicit(const A: TDocValue): IDocDict;
|
|
begin
|
|
result := DocDictFrom(A.V^);
|
|
end;
|
|
|
|
class operator TDocValue.Implicit(const A: TDocValue): variant;
|
|
begin
|
|
result := A.V^;
|
|
end;
|
|
|
|
class operator TDocValue.Implicit(const A: TDocValue): PVarData;
|
|
begin
|
|
result := pointer(A.V);
|
|
end;
|
|
|
|
class operator TDocValue.Implicit(const A: TDocValue): PDocVariantData;
|
|
begin
|
|
result := _Safe(A.V^); // better not inlined at TDocValue level
|
|
end;
|
|
|
|
{ TDocValueEnumerator }
|
|
|
|
function TDocValueEnumerator.MoveNext: boolean;
|
|
var
|
|
c: PVariant;
|
|
begin
|
|
c := Curr.V;
|
|
inc(c);
|
|
Curr.V := c;
|
|
result := PtrUInt(c) < PtrUInt(After.V);
|
|
end;
|
|
|
|
function TDocValueEnumerator.GetEnumerator: TDocValueEnumerator;
|
|
begin
|
|
result := self; // just copy 2 pointers
|
|
end;
|
|
|
|
{ TDocObjectEnumerator }
|
|
|
|
function TDocObjectEnumerator.MoveNext: boolean;
|
|
var
|
|
c, o: PVariant;
|
|
v: TDocDict;
|
|
dv: PDocVariantData;
|
|
begin
|
|
result := false;
|
|
repeat
|
|
c := Curr;
|
|
inc(c);
|
|
Curr := c;
|
|
if PtrUInt(c) >= PtrUInt(After) then
|
|
exit; // reached end of list
|
|
if not _Safe(c^, dv) then
|
|
continue
|
|
else if CompKey = '' then
|
|
begin
|
|
if not dv.IsObject then
|
|
continue; // ignore any list element which is not a IDocDict
|
|
end
|
|
else
|
|
begin
|
|
if CompKeyHasPath then
|
|
begin
|
|
o := dv^.GetPVariantByPath(CompKey, '.');
|
|
if o = nil then
|
|
continue;
|
|
end
|
|
else if not dv^.GetObjectProp(CompKey, o, @CompKeyPrev) then
|
|
continue;
|
|
if not SortMatch(CompFunc({%H-}o^, CompValue), CompMatch) then
|
|
continue;
|
|
end;
|
|
if CurrDict = nil then
|
|
begin
|
|
v := TDocDict.CreateByRef(nil);
|
|
CurrDictValue := @v.fValue;
|
|
CurrDict := v; // share a single TDocDict instance during loop
|
|
end;
|
|
CurrDictValue^ := dv; // directly change TDocDict.fValue
|
|
result := true;
|
|
exit;
|
|
until false;
|
|
end;
|
|
|
|
function TDocObjectEnumerator.GetEnumerator: TDocObjectEnumerator;
|
|
begin
|
|
result := self;
|
|
end;
|
|
|
|
{ TDocKey }
|
|
|
|
function TDocKey.Equals(const txt: RawUtf8): boolean;
|
|
begin
|
|
result := txt = V^;
|
|
end;
|
|
|
|
function TDocKey.Utf8: RawUtf8;
|
|
begin
|
|
result := V^;
|
|
end;
|
|
|
|
class operator TDocKey.Implicit(const A: TDocKey): string;
|
|
begin
|
|
Utf8ToStringVar(A.V^, result);
|
|
end;
|
|
|
|
class operator TDocKey.Implicit(const A: TDocKey): RawUtf8;
|
|
begin
|
|
result := A.V^;
|
|
end;
|
|
|
|
{ TDocDictFields }
|
|
|
|
function TDocDictFields.KeyValue(const separator: RawUtf8): RawUtf8;
|
|
begin
|
|
Make([Key.V^, separator, Value.V^], result);
|
|
end;
|
|
|
|
{ TDocKeyEnumerator }
|
|
|
|
function TDocKeyEnumerator.MoveNext: boolean;
|
|
var
|
|
c: PRawUtf8;
|
|
begin
|
|
c := Curr.V;
|
|
inc(c);
|
|
Curr.V := c;
|
|
result := PtrUInt(c) < PtrUInt(After.V);
|
|
end;
|
|
|
|
function TDocKeyEnumerator.GetEnumerator: TDocKeyEnumerator;
|
|
begin
|
|
result := self; // just copy 2 pointers
|
|
end;
|
|
|
|
{ TDocDictEnumerator }
|
|
|
|
function TDocDictEnumerator.MoveNext: boolean;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
inc(Curr.Key.V);
|
|
v := Curr.Value.V;
|
|
inc(v);
|
|
Curr.Value.V := v;
|
|
result := PtrUInt(v) < PtrUInt(AfterValue.V);
|
|
end;
|
|
|
|
function TDocDictEnumerator.GetEnumerator: TDocDictEnumerator;
|
|
begin
|
|
result := self; // just copy 3 pointers
|
|
end;
|
|
|
|
{$endif HASIMPLICITOPERATOR}
|
|
|
|
|
|
{ IDocList factories functions }
|
|
|
|
function DocList(model: TDocVariantModel): IDocList;
|
|
var
|
|
v: TDocList;
|
|
begin
|
|
v := TDocList.CreateOwned;
|
|
TDocVariantData(v.fValueOwned).Init(model, dvArray);
|
|
result := v;
|
|
end;
|
|
|
|
function DocList(const json: RawUtf8; model: TDocVariantModel): IDocList;
|
|
begin
|
|
result := DocList(model);
|
|
result.SetJson(json);
|
|
end;
|
|
|
|
function DocListFromResults(const json: RawUtf8;
|
|
model: TDocVariantModel): IDocList;
|
|
begin
|
|
result := TDocList.CreateOwned;
|
|
if not result.Value^.InitArrayFromResults(json, model) then
|
|
result := nil;
|
|
end;
|
|
|
|
function DocList(const dv: TDocVariantData): IDocList;
|
|
var
|
|
v: TDocList;
|
|
begin
|
|
v := nil;
|
|
if dv.IsArray then
|
|
v := TDocList.CreateByRef(@dv);
|
|
result := v;
|
|
end;
|
|
|
|
function DocListCopy(const dv: TDocVariantData): IDocList;
|
|
var
|
|
v: TDocList;
|
|
begin
|
|
v := nil;
|
|
if dv.IsArray then
|
|
v := TDocList.CreateCopy(dv);
|
|
result := v;
|
|
end;
|
|
|
|
function DocListCopy(const v: variant): IDocList;
|
|
begin
|
|
result := DocListCopy(_Safe(v)^);
|
|
end;
|
|
|
|
function DocListCopy(const dv: TDocVariantData; model: TDocVariantModel): IDocList;
|
|
var
|
|
v: TDocList;
|
|
begin
|
|
v := nil;
|
|
if dv.IsArray then
|
|
v := TDocList.CreateNew(dv, model);
|
|
result := v;
|
|
end;
|
|
|
|
function DocList(const values: array of const; model: TDocVariantModel): IDocList;
|
|
begin
|
|
result := TDocList.CreateOwned;
|
|
result.Value^.InitArray(values, model);
|
|
end;
|
|
|
|
function DocListFrom(const v: variant): IDocList;
|
|
var
|
|
dv: PDocVariantData;
|
|
d: TDocList;
|
|
begin
|
|
d := nil;
|
|
if _SafeArray(variant(v), dv) then
|
|
d := TDocList.CreateByRef(dv);
|
|
result := d;
|
|
end;
|
|
|
|
function DocListFrom(const dictarray: IDocDictDynArray): IDocList;
|
|
var
|
|
i: PtrInt;
|
|
begin
|
|
result := DocList(DocAnyDefaultModel);
|
|
for i := 0 to length(dictarray) - 1 do
|
|
result.AppendDoc(dictarray[i]);
|
|
end;
|
|
|
|
{ IDocDict factories functions }
|
|
|
|
function DocDict(model: TDocVariantModel): IDocDict;
|
|
var
|
|
v: TDocDict;
|
|
begin
|
|
v := TDocDict.CreateOwned;
|
|
TDocVariantData(v.fValueOwned).Init(model, dvObject);
|
|
result := v;
|
|
end;
|
|
|
|
function DocDict(const json: RawUtf8; model: TDocVariantModel): IDocDict;
|
|
begin
|
|
result := DocDict(model);
|
|
result.SetJson(json);
|
|
end;
|
|
|
|
function DocDictDynArray(const json: RawUtf8;
|
|
model: TDocVariantModel; jsonfromresults: boolean): IDocDictDynArray;
|
|
var
|
|
main: TDocVariantData;
|
|
n, i: PtrInt;
|
|
p: PVariant;
|
|
dv: PDocVariantData;
|
|
v: TDocDict;
|
|
begin
|
|
result := nil;
|
|
if jsonfromresults then
|
|
begin
|
|
if not main.InitArrayFromResults(json, model) then
|
|
exit;
|
|
end
|
|
else if not main.InitJson(json, model) or
|
|
not main.IsArray then
|
|
exit;
|
|
n := main.Count;
|
|
if n = 0 then
|
|
exit;
|
|
p := pointer(main.VValue);
|
|
i := 0;
|
|
repeat
|
|
if _SafeObject(p^, dv) then
|
|
begin
|
|
if result = nil then
|
|
SetLength(result, n); // allocate only when needed
|
|
v := TDocDict.CreateOwned;
|
|
v.fValueOwned := PVarData(dv)^; // raw copy with no refcount
|
|
PRttiVarData(dv)^.VType := varEmpty; // not in main any more
|
|
result[i] := v;
|
|
inc(i);
|
|
end;
|
|
inc(p);
|
|
dec(n);
|
|
until n = 0;
|
|
if i <> 0 then
|
|
DynArrayFakeLength(result, i); // no realloc
|
|
end;
|
|
|
|
function DocDictFrom(const v: variant): IDocDict;
|
|
var
|
|
dv: PDocVariantData;
|
|
d: TDocDict;
|
|
begin
|
|
d := nil;
|
|
if _SafeObject(variant(v), dv) then
|
|
d := TDocDict.CreateByRef(dv);
|
|
result := d;
|
|
end;
|
|
|
|
function DocDict(const dv: TDocVariantData): IDocDict;
|
|
begin
|
|
if dv.IsObject then
|
|
result := TDocDict.CreateByRef(@dv)
|
|
else
|
|
result := nil;
|
|
end;
|
|
|
|
function DocDictCopy(const dv: TDocVariantData): IDocDict;
|
|
var
|
|
d: TDocDict;
|
|
begin
|
|
d := nil;
|
|
if dv.IsObject then
|
|
d := TDocDict.CreateCopy(dv);
|
|
result := d;
|
|
end;
|
|
|
|
function DocDictCopy(const v: variant): IDocDict;
|
|
begin
|
|
result := DocDictCopy(_Safe(v)^);
|
|
end;
|
|
|
|
function DocDictCopy(const dv: TDocVariantData; model: TDocVariantModel): IDocDict;
|
|
var
|
|
d: TDocDict;
|
|
begin
|
|
d := nil;
|
|
if dv.IsObject then
|
|
d := TDocDict.CreateNew(dv, model);
|
|
result := d;
|
|
end;
|
|
|
|
function DocDict(const keyvalues: array of const; model: TDocVariantModel): IDocDict;
|
|
begin
|
|
result := TDocDict.CreateOwned;
|
|
result.Value^.InitObject(keyvalues, model);
|
|
end;
|
|
|
|
function DocDictFromKeys(const keys: array of RawUtf8;
|
|
model: TDocVariantModel): IDocDict;
|
|
begin
|
|
result := DocDictFromKeys(keys, Null, model);
|
|
end;
|
|
|
|
function DocDictFromKeys(const keys: array of RawUtf8; const value: variant;
|
|
model: TDocVariantModel): IDocDict;
|
|
var
|
|
i: PtrInt;
|
|
dv: PDocVariantData;
|
|
begin
|
|
result := TDocDict.CreateOwned;
|
|
dv := result.Value;
|
|
dv^.Init(model, dvObject);
|
|
dv^.SetCapacity(length(keys));
|
|
for i := 0 to high(keys) do
|
|
dv^.AddOrUpdateValue(keys[i], value);
|
|
end;
|
|
|
|
|
|
{ TDocAny }
|
|
|
|
constructor TDocAny.CreateOwned;
|
|
begin
|
|
fValue := @fValueOwned;
|
|
end;
|
|
|
|
procedure TDocAny.OwnedAs(opt: PDocVariantOptions; added: TDocVariantOption);
|
|
begin
|
|
fValue := @fValueOwned;
|
|
if opt = nil then
|
|
opt := @JSON_[DocAnyDefaultModel];
|
|
TRttiVarData(fValueOwned).VType := DocVariantVType +
|
|
cardinal(PWord(opt)^ + 1 shl ord(added)) shl 16; // VType+VOptions
|
|
end;
|
|
|
|
constructor TDocAny.CreateNew(const dv: TDocVariantData; m: TDocVariantModel);
|
|
begin
|
|
fValue := @fValueOwned;
|
|
TDocVariantData(fValueOwned).Init(m, dv.Kind); // new arrays, but byref values
|
|
if dv.Count = 0 then
|
|
exit;
|
|
DynArrayCopy(@fValue^.VName, @dv.VName, TypeInfo(TRawUtf8DynArray), @dv.Count);
|
|
DynArrayCopy(@fValue^.VValue, @dv.VValue, TypeInfo(TVariantDynArray), @dv.Count);
|
|
TDocVariantData(fValueOwned).VCount := dv.Count;
|
|
end;
|
|
|
|
constructor TDocAny.CreateCopy(const dv: TDocVariantData);
|
|
begin
|
|
fValue := @fValueOwned;
|
|
TDocVariantData(fValueOwned).InitFrom(dv, true, true); // new arrays, but byref values
|
|
end;
|
|
|
|
constructor TDocAny.CreateByRef(dv: PDocVariantData);
|
|
begin
|
|
fValue := dv;
|
|
end;
|
|
|
|
destructor TDocAny.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
if fValue = @fValueOwned then
|
|
TDocVariantData(fValueOwned).Void;
|
|
end;
|
|
|
|
function TDocAny.Kind: TDocVariantKind;
|
|
begin
|
|
result := fValue^.GetKind;
|
|
end;
|
|
|
|
function TDocAny.Model: TDocVariantModel;
|
|
begin
|
|
if not fValue^.GetModel(result) then
|
|
result := DocAnyDefaultModel; // default value if not exactly found
|
|
end;
|
|
|
|
function TDocAny.Len: integer;
|
|
begin
|
|
result := fValue^.VCount;
|
|
end;
|
|
|
|
procedure TDocAny.ToJson(W: TJsonWriter; Options: TTextWriterWriteObjectOptions);
|
|
begin
|
|
DocVariantType.ToJson(W, PVarData(fValue));
|
|
end;
|
|
|
|
function TDocAny.Value: PDocVariantData;
|
|
begin
|
|
result := fValue;
|
|
end;
|
|
|
|
procedure TDocAny.Clear;
|
|
begin
|
|
fValue^.Void; // keep Options and Kind
|
|
end;
|
|
|
|
function TDocAny.AsList: IDocList;
|
|
begin
|
|
if fValue^.IsArray then
|
|
result := self as TDocList
|
|
else
|
|
result := nil;
|
|
end;
|
|
|
|
function TDocAny.AsDict: IDocDict;
|
|
begin
|
|
if fValue^.IsObject then
|
|
result := self as TDocDict
|
|
else
|
|
result := nil;
|
|
end;
|
|
|
|
function TDocAny.AsVariant: variant;
|
|
begin
|
|
result := PVariant(fValue)^;
|
|
end;
|
|
|
|
procedure JL_IDocAny(var Context: TJsonParserContext;
|
|
Doc: PDocVariantData; Token: TJsonToken);
|
|
var
|
|
ctx: TGetJsonField absolute Context; // circumvent USERECORDWITHMETHODS
|
|
opt: PDocVariantOptions;
|
|
begin
|
|
Doc^.Void; // IDocList/IDocDict may be existing and with some previous data
|
|
if GetFirstJsonToken(ctx.Json) <> Token then
|
|
begin
|
|
Context.Valid := (ctx.Json <> nil) and Context.ParseNull;
|
|
exit;
|
|
end;
|
|
opt := Context.CustomVariant;
|
|
if opt = nil then
|
|
opt := @Doc^.VOptions;
|
|
ctx.Json := Doc^.InitJsonInPlace(ctx.Json, opt^, @ctx.EndOfObject);
|
|
Context.Valid := ctx.Json <> nil;
|
|
end;
|
|
|
|
|
|
{ EDocList }
|
|
|
|
class procedure EDocList.GetRaise(method: AnsiChar; pos: integer; const v: variant);
|
|
begin
|
|
raise CreateUtf8('%[%] on a var%', [method, pos, VariantTypeName(v)^]);
|
|
end;
|
|
|
|
{ TDocList }
|
|
|
|
constructor TDocList.Create(options: PDocVariantOptions);
|
|
begin
|
|
OwnedAs(options, dvoIsArray);
|
|
end;
|
|
|
|
procedure TDocList.FromJson(var context: TJsonParserContext);
|
|
begin
|
|
JL_IDocAny(context, fValue, jtArrayStart);
|
|
end;
|
|
|
|
function TDocList.ValueAt(position: integer): PVariant;
|
|
var
|
|
ndx, n: PtrUInt;
|
|
begin
|
|
ndx := position;
|
|
n := fValue^.VCount;
|
|
if position < 0 then
|
|
inc(ndx, n);
|
|
if ndx >= n then
|
|
raise EDocList.CreateUtf8('Index % out of range (len=%)', [position, n]);
|
|
result := @fValue^.VValue[ndx];
|
|
// setters should not call EnsureUnique() because is done in constructor
|
|
end;
|
|
|
|
function TDocList.GetItem(position: integer): variant;
|
|
begin
|
|
result := ValueAt(position)^;
|
|
end;
|
|
|
|
procedure TDocList.SetItem(position: integer; const value: variant);
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := ValueAt(position);
|
|
SetVariantByValue(value, v^); // may convert to RawUtf8/varString
|
|
if (PVarData(v)^.VType = varString) and
|
|
fValue^.Has(dvoInternValues) then
|
|
InternalUniqueValue(v);
|
|
end;
|
|
|
|
function TDocList.GetU(position: integer): RawUtf8;
|
|
begin
|
|
VariantToUtf8(ValueAt(position)^, result);
|
|
end;
|
|
|
|
procedure TDocList.SetU(position: integer; const value: RawUtf8);
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := ValueAt(position);
|
|
RawUtf8ToVariant(value, v^);
|
|
if fValue^.Has(dvoInternValues) then
|
|
InternalUniqueValue(v);
|
|
end;
|
|
|
|
function TDocList.GetS(position: integer): string;
|
|
begin
|
|
VariantToString(ValueAt(position)^, result);
|
|
end;
|
|
|
|
procedure TDocList.SetS(position: integer; const value: string);
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := ValueAt(position);
|
|
StringToVariant(value, v^); // convert and store as RawUtf8/varString
|
|
if fValue^.Has(dvoInternValues) then
|
|
InternalUniqueValue(v);
|
|
end;
|
|
|
|
function TDocList.GetI(position: integer): Int64;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := ValueAt(position);
|
|
if not VariantToInt64(v^, result) then
|
|
EDocList.GetRaise('I', position, v^);
|
|
end;
|
|
|
|
function TDocList.GetF(position: integer): double;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := ValueAt(position);
|
|
if not VariantToDouble(v^, result) then
|
|
EDocList.GetRaise('F', position, v^);
|
|
end;
|
|
|
|
function TDocList.GetC(position: integer): currency;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := ValueAt(position);
|
|
if not VariantToCurrency(v^, result) then
|
|
EDocList.GetRaise('C', position, v^);
|
|
end;
|
|
|
|
procedure TDocList.SetI(position: integer; value: Int64);
|
|
begin
|
|
ValueAt(position)^ := value;
|
|
end;
|
|
|
|
procedure TDocList.SetF(position: integer; const value: double);
|
|
begin
|
|
ValueAt(position)^ := value;
|
|
end;
|
|
|
|
procedure TDocList.SetC(position: integer; const value: currency);
|
|
begin
|
|
ValueAt(position)^ := value;
|
|
end;
|
|
|
|
function TDocList.GetB(position: integer): boolean;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := ValueAt(position);
|
|
if not VariantToBoolean(v^, result) then
|
|
EDocList.GetRaise('B', position, v^);
|
|
end;
|
|
|
|
function TDocList.GetL(position: integer): IDocList;
|
|
begin
|
|
result := TDocList.CreateByRef(_Safe(ValueAt(position)^, dvArray));
|
|
end;
|
|
|
|
function TDocList.GetD(position: integer): IDocDict;
|
|
begin
|
|
result := TDocDict.CreateByRef(_Safe(ValueAt(position)^, dvObject));
|
|
end;
|
|
|
|
procedure TDocList.SetB(position: integer; value: boolean);
|
|
begin
|
|
ValueAt(position)^ := value;
|
|
end;
|
|
|
|
procedure TDocList.SetL(position: integer; const value: IDocList);
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := ValueAt(position);
|
|
if value = nil then
|
|
VarClear(v^)
|
|
else
|
|
v^ := PVariant(value.Value)^;
|
|
end;
|
|
|
|
procedure TDocList.SetD(position: integer; const value: IDocDict);
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := ValueAt(position);
|
|
if value = nil then
|
|
VarClear(v^)
|
|
else
|
|
v^ := PVariant(value.Value)^;
|
|
end;
|
|
|
|
function TDocList.Append(const value: variant): integer;
|
|
begin
|
|
result := fValue^.AddItem(value);
|
|
end;
|
|
|
|
function TDocList.Append(const value: RawUtf8): integer;
|
|
begin
|
|
result := fValue^.AddItemText(value);
|
|
end;
|
|
|
|
function TDocList.AppendDoc(const value: IDocAny): integer;
|
|
begin
|
|
result := fValue^.AddItem(PVariant(value.Value)^);
|
|
end;
|
|
|
|
function DocListRangeVoid(var start, stop: integer; n: integer): boolean;
|
|
begin
|
|
result := true;
|
|
if n = 0 then
|
|
exit;
|
|
if start < 0 then
|
|
inc(start, n);
|
|
if stop <> 0 then
|
|
begin
|
|
if stop < 0 then
|
|
inc(stop, n);
|
|
dec(stop, start); // from index to limit, excluding stop position
|
|
if stop <= 0 then
|
|
exit;
|
|
end;
|
|
result := false; // not void
|
|
end;
|
|
|
|
function TDocList.Copy(start, stop: integer): IDocList;
|
|
begin
|
|
result := TDocList.CreateOwned;
|
|
if DocListRangeVoid(start, stop, fValue^.Count) then
|
|
result.Value^.Init(fValue^.VOptions, dvArray)
|
|
else
|
|
result.Value^.InitArrayFrom(fValue^, fValue^.VOptions, start, stop);
|
|
end;
|
|
|
|
function TDocList.Compare(const another: IDocList; caseinsensitive: boolean): integer;
|
|
begin
|
|
if another = nil then
|
|
result := 1
|
|
else if another.Value = fValue then
|
|
result := 0 // same reference
|
|
else
|
|
result := fValue^.Compare(another.Value^, caseinsensitive);
|
|
end;
|
|
|
|
function TDocList.Count(const value: variant): integer;
|
|
begin
|
|
result := fValue^.CountItemByValue(value);
|
|
end;
|
|
|
|
function TDocList.Count(const value: RawUtf8): integer;
|
|
var
|
|
v: TRttiVarData;
|
|
begin
|
|
v.VType := varString;
|
|
v.Data.VAny := pointer(value); // direct set to our RawUtf8 searched value
|
|
result := fValue^.CountItemByValue(variant(v));
|
|
end;
|
|
|
|
procedure TDocList.Extend(const value: IDocList);
|
|
begin
|
|
if value <> nil then
|
|
fValue^.AddFrom(variant(value.Value^));
|
|
end;
|
|
|
|
procedure TDocList.Extend(const value: array of const);
|
|
begin
|
|
fValue^.AddItems(value);
|
|
end;
|
|
|
|
function TDocList.Index(const value: variant): integer;
|
|
begin
|
|
result := fValue^.SearchItemByValue(value);
|
|
end;
|
|
|
|
function TDocList.Index(const value: RawUtf8; caseinsensitive: boolean): integer;
|
|
var
|
|
v: TRttiVarData;
|
|
begin
|
|
v.VType := varString;
|
|
v.Data.VAny := pointer(value); // direct set to our RawUtf8 searched value
|
|
result := fValue^.SearchItemByValue(variant(v), caseinsensitive);
|
|
end;
|
|
|
|
function TDocList.Exists(const value: variant): boolean;
|
|
begin
|
|
result := fValue^.SearchItemByValue(value) >= 0;
|
|
end;
|
|
|
|
function TDocList.Exists(const value: RawUtf8; caseinsensitive: boolean): boolean;
|
|
begin
|
|
result := Index(value, caseinsensitive) >= 0;
|
|
end;
|
|
|
|
function TDocList.Insert(position: integer; const value: variant): integer;
|
|
begin
|
|
result := fValue^.AddItem(value, position);
|
|
end;
|
|
|
|
function TDocList.Insert(position: integer; const value: RawUtf8): integer;
|
|
begin
|
|
result := fValue^.AddItemText(value, position);
|
|
end;
|
|
|
|
function TDocList.ObjectsDictDynArray: IDocDictDynArray;
|
|
var
|
|
n, i: PtrInt;
|
|
p: PVariant;
|
|
dv: PDocVariantData;
|
|
begin
|
|
result := nil;
|
|
n := fValue^.Count;
|
|
if n = 0 then
|
|
exit;
|
|
p := pointer(fValue^.VValue);
|
|
i := 0;
|
|
repeat
|
|
if _SafeObject(p^, dv) then
|
|
begin
|
|
if result = nil then
|
|
SetLength(result, n); // allocate only when needed
|
|
result[i] := TDocDict.CreateByRef(dv);
|
|
inc(i);
|
|
end;
|
|
inc(p);
|
|
dec(n);
|
|
until n = 0;
|
|
if i <> 0 then
|
|
DynArrayFakeLength(result, i); // no realloc
|
|
end;
|
|
|
|
function TDocList.Pop(position: integer): variant;
|
|
begin
|
|
if not fValue^.Extract(position, result) then
|
|
raise EDocList.CreateUtf8('Pop index % out of range', [position]);
|
|
end;
|
|
|
|
function TDocList.PopItem(out value: variant; position: integer): boolean;
|
|
begin
|
|
result := fValue^.Extract(position, value);
|
|
end;
|
|
|
|
function TDocList.PopItem(out value: IDocDict; position: integer): boolean;
|
|
begin
|
|
result := false;
|
|
if position < 0 then
|
|
inc(position, fValue^.Count);
|
|
if (cardinal(position) >= cardinal(fValue^.Count)) or
|
|
not _Safe(fValue^.VValue[position]).IsObject then
|
|
exit;
|
|
value := TDocDict.CreateOwned;
|
|
result := fValue^.Extract(position, PVariant(value.Value)^);
|
|
end;
|
|
|
|
function TDocList.Del(position: integer): boolean;
|
|
begin
|
|
if position < 0 then
|
|
inc(position, fValue^.Count);
|
|
result := fValue^.Delete(position);
|
|
end;
|
|
|
|
function TDocList.Reduce(const keys: array of RawUtf8): IDocList;
|
|
begin
|
|
result := DocList(Model);
|
|
fValue^.Reduce(keys, fValue^.IsCaseSensitive, result.Value^);
|
|
end;
|
|
|
|
function TDocList.Remove(const value: variant): integer;
|
|
begin
|
|
result := fValue^.SearchItemByValue(value);
|
|
if result >= 0 then
|
|
fValue^.Delete(result);
|
|
end;
|
|
|
|
function TDocList.Remove(const value: RawUtf8; caseinsensitive: boolean): integer;
|
|
begin
|
|
result := Index(value, caseinsensitive);
|
|
if result >= 0 then
|
|
fValue^.Delete(result);
|
|
end;
|
|
|
|
procedure TDocList.Reverse;
|
|
begin
|
|
fValue^.Reverse;
|
|
end;
|
|
|
|
procedure TDocList.Sort(reverse: boolean; compare: TVariantCompare);
|
|
begin
|
|
fValue^.SortByValue(compare, reverse);
|
|
end;
|
|
|
|
procedure TDocList.SortByKeyValue(const key: RawUtf8; reverse: boolean;
|
|
compare: TVariantCompare);
|
|
begin
|
|
fValue^.SortArrayByField(key, compare, reverse);
|
|
end;
|
|
|
|
procedure TDocList.SortByKeyValue(const keys: array of RawUtf8;
|
|
reverse: boolean; compare: TVariantCompare);
|
|
begin
|
|
fValue^.SortArrayByFields(keys, compare, nil, reverse);
|
|
end;
|
|
|
|
function TDocList.Filter(const key: RawUtf8; const value: variant;
|
|
limit: integer; match: TCompareOperator; compare: TVariantCompare): IDocList;
|
|
begin
|
|
result := TDocList.CreateOwned;
|
|
fValue^.ReduceFilter(key, value, match, compare, limit, result.Value^);
|
|
end;
|
|
|
|
function TDocList.Filter(const expression: RawUtf8): IDocList;
|
|
begin // no limit here to avoid confusion between overloads
|
|
result := TDocList.CreateOwned;
|
|
fValue^.ReduceFilter(expression, result.Value^);
|
|
end;
|
|
|
|
function TDocList.Filter(const expression: RawUtf8; const value: variant;
|
|
limit: integer): IDocList;
|
|
begin
|
|
result := TDocList.CreateOwned;
|
|
fValue^.ReduceFilter(expression, value, result.Value^, nil, limit);
|
|
end;
|
|
|
|
function TDocList.First(const expression: RawUtf8): variant;
|
|
begin
|
|
result := fValue^.ReduceFilter(expression, {limit=} 1);
|
|
end;
|
|
|
|
function TDocList.First(const expression: RawUtf8; const value: variant): variant;
|
|
begin
|
|
result := fValue^.ReduceFilter(expression, value, {limit=} 1);
|
|
end;
|
|
|
|
{$ifdef HASIMPLICITOPERATOR}
|
|
|
|
function TDocList.GetV(position: integer): TDocValue;
|
|
begin
|
|
result.V := ValueAt(position);
|
|
end;
|
|
|
|
procedure SetValueEnumerator(dv: PDocVariantData; var res: TDocValueEnumerator);
|
|
{$ifdef HASINLINE} inline; {$endif}
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := pointer(dv^.VValue);
|
|
res.Curr.V := v;
|
|
res.After.V := v;
|
|
if v = nil then
|
|
exit;
|
|
inc(res.After.V, dv^.VCount);
|
|
dec(res.Curr.V); // for the first MoveNext
|
|
end;
|
|
|
|
function TDocList.GetEnumerator: TDocValueEnumerator;
|
|
begin
|
|
SetValueEnumerator(fValue, result{%H-}); // shared with IDocDict.Values
|
|
end;
|
|
|
|
function TDocList.Range(start, stop: integer): TDocValueEnumerator;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
result.Curr.V := nil;
|
|
result.After.V := nil; // ensure MoveNext=false on void range
|
|
if DocListRangeVoid(start, stop, fValue^.Count) or
|
|
fValue^.RangeVoid(start, stop) then
|
|
exit;
|
|
v := pointer(fValue^.VValue);
|
|
inc(v, start);
|
|
dec(v); // for the first MoveNext
|
|
result.Curr.V := v;
|
|
inc(v, stop + 1);
|
|
result.After.V := v;
|
|
end;
|
|
|
|
function TDocList.Objects: TDocObjectEnumerator;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := pointer(fValue^.VValue);
|
|
result.Curr := v;
|
|
result.After := v;
|
|
if v = nil then
|
|
exit;
|
|
inc(result.After, fValue^.VCount);
|
|
dec(result.Curr); // for the first MoveNext
|
|
end;
|
|
|
|
function TDocList.Objects(const key: RawUtf8; const value: variant;
|
|
match: TCompareOperator; compare: TVariantCompare): TDocObjectEnumerator;
|
|
begin
|
|
if key = '' then
|
|
raise EDocList.Create('Invalid expression on Objects()');
|
|
result := Objects;
|
|
result.CompKey := key;
|
|
result.CompValue := value;
|
|
if not Assigned(compare) then
|
|
compare := @VariantCompare;
|
|
result.CompFunc := compare;
|
|
result.CompMatch := match;
|
|
result.CompKeyHasPath := PosExChar('.', key) <> 0;
|
|
result.CompKeyPrev := -1; // optimistic key search in previous position
|
|
end;
|
|
|
|
function TDocList.Objects(const expression: RawUtf8): TDocObjectEnumerator;
|
|
var
|
|
k: RawUtf8;
|
|
v: variant;
|
|
m: TCompareOperator;
|
|
begin
|
|
ParseSortMatch(pointer(expression), k, m, @v);
|
|
result := Objects(k, v, m, nil);
|
|
end;
|
|
|
|
function TDocList.Objects(const expression: RawUtf8;
|
|
const value: variant): TDocObjectEnumerator;
|
|
var
|
|
k: RawUtf8;
|
|
m: TCompareOperator;
|
|
begin
|
|
ParseSortMatch(pointer(expression), k, m, nil);
|
|
result := Objects(k, value, m, nil);
|
|
end;
|
|
|
|
{$endif HASIMPLICITOPERATOR}
|
|
|
|
|
|
|
|
|
|
{ EDocDict }
|
|
|
|
class procedure EDocDict.Error(method: AnsiChar; const key: RawUtf8; const v: variant);
|
|
begin
|
|
raise CreateUtf8('%[%] on a var%', [method, key, VariantTypeName(v)^]);
|
|
end;
|
|
|
|
{ TDocDict }
|
|
|
|
constructor TDocDict.Create(options: PDocVariantOptions);
|
|
begin
|
|
OwnedAs(options, dvoIsObject);
|
|
end;
|
|
|
|
procedure TDocDict.FromJson(var context: TJsonParserContext);
|
|
begin
|
|
JL_IDocAny(context, fValue, jtObjectStart);
|
|
end;
|
|
|
|
function TDocDict.GetPathDelim: AnsiChar;
|
|
begin
|
|
result := fPathDelim;
|
|
end;
|
|
|
|
procedure TDocDict.SetPathDelim(value: AnsiChar);
|
|
begin
|
|
fPathDelim := value;
|
|
end;
|
|
|
|
function TDocDict.Compare(const another: IDocDict; caseinsensitive: boolean): integer;
|
|
begin
|
|
result := fValue^.Compare(another.Value^, caseinsensitive);
|
|
end;
|
|
|
|
function TDocDict.Compare(const another: IDocDict;
|
|
const keys: array of RawUtf8; caseinsensitive: boolean): integer;
|
|
begin
|
|
result := fValue^.CompareObject(keys, another.Value^, caseinsensitive);
|
|
end;
|
|
|
|
function TDocDict.GetValueAt(const key: RawUtf8; out value: PVariant): boolean;
|
|
begin
|
|
if fPathDelim = #0 then
|
|
value := pointer(fValue^.GetVarData(key, fSorted)) // faster
|
|
else
|
|
value := fValue^.GetPVariantByPath(key, fPathDelim);
|
|
result := value <> nil; // return false if not found
|
|
end;
|
|
|
|
function TDocDict.GetExistingValueAt(const key, method: RawUtf8): PVariant;
|
|
begin
|
|
if not GetValueAt(key, result) then
|
|
if fValue^.Has(dvoReturnNullForUnknownProperty) then
|
|
result := @DocVariantDataFake
|
|
else
|
|
raise EDocDict.CreateUtf8('%[''%''] key not found', [method, key]);
|
|
end;
|
|
|
|
function TDocDict.ValueAt(const key: RawUtf8): PVariant;
|
|
begin
|
|
result := GetExistingValueAt(key, 'ValueAt');
|
|
end;
|
|
|
|
function TDocDict.SetValueAt(const key: RawUtf8; const value: variant): boolean;
|
|
begin
|
|
if fPathDelim = #0 then
|
|
result := fValue^.AddOrUpdateValue(key, value) >= 0
|
|
else
|
|
result := fValue^.SetValueByPath(key, value, {create=}true, fPathDelim);
|
|
if result then
|
|
fSorted := nil;
|
|
end;
|
|
|
|
function TDocDict.PopAt(const key: RawUtf8; value: PVariant): boolean;
|
|
begin
|
|
if fPathDelim = #0 then
|
|
result := fValue.Delete(key, value)
|
|
else
|
|
result := fValue.DeleteByPath(key, fPathDelim, value);
|
|
end;
|
|
|
|
function TDocDict.GetB(const key: RawUtf8): boolean;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := GetExistingValueAt(key, 'B');
|
|
if not VariantToBoolean(v^, result) then
|
|
EDocDict.Error('B', key, v^);
|
|
end;
|
|
|
|
function TDocDict.GetC(const key: RawUtf8): currency;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := GetExistingValueAt(key, 'C');
|
|
if not VariantToCurrency(v^, result) then
|
|
EDocDict.Error('C', key, v^);
|
|
end;
|
|
|
|
function TDocDict.GetD(const key: RawUtf8): IDocDict;
|
|
begin
|
|
result := TDocDict.CreateByRef(
|
|
_Safe(GetExistingValueAt(key, 'D')^, dvObject));
|
|
end;
|
|
|
|
function TDocDict.GetF(const key: RawUtf8): double;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := GetExistingValueAt(key, 'F');
|
|
if not VariantToDouble(v^, result) then
|
|
EDocDict.Error('F', key, v^);
|
|
end;
|
|
|
|
function TDocDict.GetI(const key: RawUtf8): Int64;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := GetExistingValueAt(key, 'I');
|
|
if not VariantToInt64(v^, result) then
|
|
EDocDict.Error('I', key, v^);
|
|
end;
|
|
|
|
function TDocDict.GetItem(const key: RawUtf8): variant;
|
|
begin
|
|
result := GetExistingValueAt(key, 'Item')^;
|
|
end;
|
|
|
|
function TDocDict.GetL(const key: RawUtf8): IDocList;
|
|
begin
|
|
result := TDocList.CreateByRef(
|
|
_Safe(GetExistingValueAt(key, 'B')^, dvArray));
|
|
end;
|
|
|
|
function TDocDict.GetS(const key: RawUtf8): string;
|
|
begin
|
|
VariantToString(GetExistingValueAt(key, 'S')^, result);
|
|
end;
|
|
|
|
function TDocDict.GetU(const key: RawUtf8): RawUtf8;
|
|
begin
|
|
VariantToUtf8(GetExistingValueAt(key, 'U')^, result);
|
|
end;
|
|
|
|
procedure TDocDict.SetB(const key: RawUtf8; const value: boolean);
|
|
begin
|
|
SetValueAt(key, value);
|
|
end;
|
|
|
|
procedure TDocDict.SetC(const key: RawUtf8; const value: currency);
|
|
begin
|
|
SetValueAt(key, value);
|
|
end;
|
|
|
|
procedure TDocDict.SetD(const key: RawUtf8; const value: IDocDict);
|
|
begin
|
|
if value = nil then
|
|
EDocDict.Error('D', key, Null);
|
|
SetValueAt(key, PVariant(value.Value)^)
|
|
end;
|
|
|
|
procedure TDocDict.SetF(const key: RawUtf8; const value: double);
|
|
begin
|
|
SetValueAt(key, value);
|
|
end;
|
|
|
|
procedure TDocDict.SetI(const key: RawUtf8; const value: Int64);
|
|
begin
|
|
SetValueAt(key, value);
|
|
end;
|
|
|
|
procedure TDocDict.SetItem(const key: RawUtf8; const value: variant);
|
|
begin
|
|
SetValueAt(key, value);
|
|
end;
|
|
|
|
procedure TDocDict.SetL(const key: RawUtf8; const value: IDocList);
|
|
begin
|
|
if value = nil then
|
|
EDocDict.Error('D', key, Null);
|
|
SetValueAt(key, PVariant(value.Value)^)
|
|
end;
|
|
|
|
procedure TDocDict.SetS(const key: RawUtf8; const value: string);
|
|
var
|
|
v: variant;
|
|
begin
|
|
StringToVariant(value, v);
|
|
SetValueAt(key, v);
|
|
end;
|
|
|
|
procedure TDocDict.SetU(const key: RawUtf8; const value: RawUtf8);
|
|
var
|
|
v: variant;
|
|
begin
|
|
RawUtf8ToVariant(value, v);
|
|
SetValueAt(key, v);
|
|
end;
|
|
|
|
function TDocDict.Get(const key: RawUtf8): variant;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
if GetValueAt(key, v) then
|
|
result := v^
|
|
else
|
|
VarClear(result);
|
|
end;
|
|
|
|
function TDocDict.GetDef(const key: RawUtf8; const default: variant): variant;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
if GetValueAt(key, v) then
|
|
result := v^
|
|
else
|
|
result := default;
|
|
end;
|
|
|
|
function TDocDict.GetDef(const key: RawUtf8; const default: RawUtf8): variant;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
if GetValueAt(key, v) then
|
|
result := v^
|
|
else
|
|
RawUtf8ToVariant(default, result);
|
|
end;
|
|
|
|
function TDocDict.Get(const key: RawUtf8; var value: variant): boolean;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
result := GetValueAt(key, v);
|
|
if result then
|
|
value := v^;
|
|
end;
|
|
|
|
function TDocDict.Get(const key: RawUtf8; var value: RawUtf8): boolean;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
result := GetValueAt(key, v);
|
|
if result then
|
|
VariantToUtf8(v^, value, {wasstring=}result);
|
|
end;
|
|
|
|
function TDocDict.Get(const key: RawUtf8; var value: string): boolean;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
result := GetValueAt(key, v);
|
|
if result then
|
|
VariantToString(v^, value);
|
|
end;
|
|
|
|
function TDocDict.Get(const key: RawUtf8; var value: boolean): boolean;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
result := GetValueAt(key, v) and
|
|
VariantToBoolean(v^, value);
|
|
end;
|
|
|
|
function TDocDict.Get(const key: RawUtf8; var value: integer): boolean;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
result := GetValueAt(key, v) and
|
|
VariantToInteger(v^, value);
|
|
end;
|
|
|
|
function TDocDict.Get(const key: RawUtf8; var value: Int64): boolean;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
result := GetValueAt(key, v) and
|
|
VariantToInt64(v^, value);
|
|
end;
|
|
|
|
function TDocDict.Get(const key: RawUtf8; var value: double): boolean;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
result := GetValueAt(key, v) and
|
|
VariantToDouble(v^, value);
|
|
end;
|
|
|
|
function TDocDict.Get(const key: RawUtf8; var value: currency): boolean;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
result := GetValueAt(key, v) and
|
|
VariantToCurrency(v^, value);
|
|
end;
|
|
|
|
function TDocDict.Get(const key: RawUtf8; var value: IDocList): boolean;
|
|
var
|
|
v: PVariant;
|
|
dv: PDocVariantData;
|
|
begin
|
|
result := GetValueAt(key, v) and
|
|
_SafeArray(v^, dv);
|
|
if result then
|
|
value := TDocList.CreateByRef(dv);
|
|
end;
|
|
|
|
function TDocDict.Get(const key: RawUtf8; var value: IDocDict): boolean;
|
|
var
|
|
v: PVariant;
|
|
dv: PDocVariantData;
|
|
begin
|
|
result := GetValueAt(key, v) and
|
|
_SafeObject(v^, dv);
|
|
if result then
|
|
value := TDocDict.CreateByRef(dv);
|
|
end;
|
|
|
|
function TDocDict.Get(const key: RawUtf8; var value: PDocVariantData): boolean;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
result := GetValueAt(key, v) and
|
|
_Safe(v^, value);
|
|
end;
|
|
|
|
function TDocDict.Copy: IDocDict;
|
|
var
|
|
v: TDocDict;
|
|
begin
|
|
v := TDocDict.CreateCopy(fValue^);
|
|
v.fPathDelim := fPathDelim; // also include additional parameters
|
|
v.fSorted := fSorted;
|
|
result := v;
|
|
end;
|
|
|
|
function TDocDict.Del(const key: RawUtf8): boolean;
|
|
begin
|
|
result := PopAt(key, nil);
|
|
end;
|
|
|
|
function TDocDict.Exists(const key: RawUtf8): boolean;
|
|
begin
|
|
if fPathDelim = #0 then
|
|
result := fValue^.GetVarData(key, fSorted) <> nil // faster
|
|
else
|
|
result := fValue^.GetPVariantByPath(key, fPathDelim) <> nil;
|
|
end;
|
|
|
|
function TDocDict.Pop(const key: RawUtf8): variant;
|
|
begin
|
|
if not PopAt(key, @result) then
|
|
raise EDocDict.CreateUtf8('Pop with unknown key [%]', [key]);
|
|
end;
|
|
|
|
function TDocDict.Pop(const key: RawUtf8; const default: variant): variant;
|
|
begin
|
|
if not PopAt(key, @result) then
|
|
result := default;
|
|
end;
|
|
|
|
function TDocDict.PopItem(out key: RawUtf8; out value: variant;
|
|
position: integer): boolean;
|
|
begin
|
|
result := fValue^.Extract(position, value, @key);
|
|
end;
|
|
|
|
function TDocDict.Reduce(const keys: array of RawUtf8): IDocDict;
|
|
begin
|
|
result := TDocDict.CreateOwned;
|
|
fValue^.Reduce(keys, fValue^.IsCaseSensitive, result.Value^);
|
|
end;
|
|
|
|
function TDocDict.SetDefault(const key: RawUtf8): variant;
|
|
begin
|
|
result := SetDefault(key, Null);
|
|
end;
|
|
|
|
function TDocDict.SetDefault(const key: RawUtf8; const default: variant): variant;
|
|
begin
|
|
if Get(key, result) then
|
|
exit;
|
|
SetValueAt(key, default);
|
|
result := default;
|
|
end;
|
|
|
|
procedure TDocDict.Sort(reverse: boolean; keycompare: TUtf8Compare);
|
|
begin
|
|
if not Assigned(keycompare) then
|
|
keycompare := StrCompByCase[fValue^.IsCaseSensitive];
|
|
fValue^.SortByName(keycompare, reverse);
|
|
if reverse then
|
|
fSorted := nil
|
|
else
|
|
fSorted := keycompare; // for O(log(n)) binary search on key lookup
|
|
end;
|
|
|
|
procedure TDocDict.Update(const key: RawUtf8; const value: variant);
|
|
begin
|
|
SetValueAt(key, value);
|
|
end;
|
|
|
|
procedure TDocDict.Update(const keyvalues: array of const);
|
|
begin
|
|
fValue^.Update(keyvalues);
|
|
end;
|
|
|
|
procedure TDocDict.Update(const source: IDocDict; addonlymissing: boolean);
|
|
begin
|
|
if source <> nil then
|
|
fValue^.AddOrUpdateFrom(PVariant(source.Value)^, addonlymissing);
|
|
end;
|
|
|
|
{$ifdef HASIMPLICITOPERATOR}
|
|
|
|
function TDocDict.GetV(const key: RawUtf8): TDocValue;
|
|
begin
|
|
result.V := GetExistingValueAt(key, 'V');
|
|
end;
|
|
|
|
function TDocDict.GetEnumerator: TDocDictEnumerator;
|
|
var
|
|
v: PVariant;
|
|
begin
|
|
v := pointer(fValue^.VValue);
|
|
result.Curr.Value.V := v;
|
|
result.AfterValue.V := v;
|
|
if v = nil then
|
|
exit;
|
|
inc(result.AfterValue.V, fValue^.VCount);
|
|
result.Curr.Key.V := pointer(fValue^.VName);
|
|
dec(result.Curr.Value.V); // for the first MoveNext
|
|
dec(result.Curr.Key.V);
|
|
end;
|
|
|
|
function TDocDict.Keys: TDocKeyEnumerator;
|
|
var
|
|
v: PRawUtf8;
|
|
begin
|
|
v := pointer(fValue^.VName);
|
|
result.Curr.V := v;
|
|
result.After.V := v;
|
|
if v = nil then
|
|
exit;
|
|
inc(result.After.V, fValue^.VCount);
|
|
dec(result.Curr.V); // for the first MoveNext
|
|
end;
|
|
|
|
function TDocDict.Values: TDocValueEnumerator;
|
|
begin
|
|
SetValueEnumerator(fValue, result{%H-}); // shared with IDocList
|
|
end;
|
|
|
|
{$endif HASIMPLICITOPERATOR}
|
|
|
|
procedure InitializeVariantsJson;
|
|
begin
|
|
// called from mormot.core.json once TRttiJson is set as global RTTI class
|
|
TDocList.RegisterToRtti(TypeInfo(IDocList));
|
|
TDocDict.RegisterToRtti(TypeInfo(IDocDict));
|
|
end;
|
|
|
|
|
|
var
|
|
// naive but efficient type cache - e.g. for TBsonVariant or TQuickJsVariant
|
|
LastDispInvoke: TSynInvokeableVariantType;
|
|
|
|
// sysdispinvoke() replacement to meet TSynInvokeableVariantType expectations
|
|
procedure NewDispInvoke(Dest: PVarData;
|
|
{$ifdef FPC_VARIANTSETVAR}
|
|
var Source: TVarData;
|
|
{$else} // see http://mantis.freepascal.org/view.php?id=26773
|
|
const Source: TVarData; // "[ref] const" on modern Delphi
|
|
{$endif FPC_VARIANTSETVAR}
|
|
CallDesc: PCallDesc; Params: pointer); cdecl;
|
|
// warning: Delphi OSX64 LINUX ANDROID64 expects Params := @VAList
|
|
var
|
|
v: TVarData;
|
|
vp: PVariant;
|
|
t: cardinal;
|
|
ct: TSynInvokeableVariantType;
|
|
label
|
|
direct;
|
|
begin
|
|
t := Source.vType;
|
|
if t = varVariantByRef then
|
|
NewDispInvoke(Dest, PVarData(Source.VPointer)^, calldesc, params)
|
|
else
|
|
begin
|
|
TRttiVarData(v).VType := varEmpty;
|
|
vp := @v;
|
|
if Dest = nil then
|
|
vp := nil;
|
|
ct := nil;
|
|
try
|
|
case t of
|
|
varDispatch,
|
|
varAny,
|
|
varUnknown,
|
|
varDispatch or varByRef,
|
|
varAny or varByRef,
|
|
varUnknown or varByRef:
|
|
if Assigned(VarDispProc) and
|
|
Assigned(VarCopyProc) then
|
|
// standard Windows ComObj unit call
|
|
VarDispProc(vp, variant(Source), CallDesc, Params)
|
|
else
|
|
VarInvalidOp;
|
|
CFirstUserType .. varTypeMask:
|
|
begin
|
|
ct := DocVariantType; // recognize our TDocVariant
|
|
if t = ct.VarType then
|
|
goto direct;
|
|
ct := LastDispInvoke; // atomic pointer load
|
|
if (ct <> nil) and
|
|
(ct.VarType = t) then
|
|
// most calls are grouped within the same custom variant type
|
|
goto direct;
|
|
// FindCustomVariantType() is O(1) but has a global lock
|
|
if FindCustomVariantType(t, TCustomVariantType(ct)) then
|
|
if ct.InheritsFrom(TSynInvokeableVariantType) then
|
|
begin
|
|
// direct access of our custom variants without any temp copy
|
|
LastDispInvoke := ct;
|
|
direct: if Dest <> nil then
|
|
VarClear(PVariant(Dest)^); // no temp copy, but Dest cleanup
|
|
ct.DispInvoke(Dest, Source, CallDesc, Params);
|
|
Dest := nil;
|
|
end
|
|
else if ct.InheritsFrom(TInvokeableVariantType) then
|
|
// use standard RTL behavior for non-mORMot custom variants
|
|
ct.DispInvoke(pointer(vp), Source, CallDesc, Params)
|
|
else
|
|
VarInvalidOp
|
|
else
|
|
VarInvalidOp;
|
|
end;
|
|
else
|
|
VarInvalidOp;
|
|
end;
|
|
finally
|
|
if Dest <> nil then
|
|
begin
|
|
if (ct <> nil) and
|
|
(v.VType = ct.VarType) then // don't search twice if we got it
|
|
ct.Copy(Dest^, v, {indirect=}false)
|
|
else
|
|
VarCopyProc(Dest^, v);
|
|
VarClear(vp^);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
const
|
|
// _CMP2SORT[] comparison of simple types - as copied to _VARDATACMP[]
|
|
_VARDATACMPNUM1: array[varEmpty..varDate] of byte = (
|
|
1, 1, 2, 3, 4, 5, 6, 7);
|
|
_VARDATACMPNUM2: array[varShortInt..varWord64] of byte = (
|
|
8, 9, 10, 11, 12, 13);
|
|
|
|
procedure InitializeUnit;
|
|
var
|
|
vm: TVariantManager; // available since Delphi 7
|
|
vt: cardinal;
|
|
ins: boolean;
|
|
i: PtrUInt;
|
|
{$ifdef FPC}
|
|
test: variant;
|
|
{$endif FPC}
|
|
begin
|
|
// register the TDocVariant custom type
|
|
DocVariantType := TDocVariant(SynRegisterCustomVariantType(TDocVariant));
|
|
vt := DocVariantType.VarType;
|
|
DocVariantVType := vt;
|
|
PCardinal(@DV_FAST[dvUndefined])^ := vt;
|
|
PCardinal(@DV_FAST[dvArray])^ := vt;
|
|
PCardinal(@DV_FAST[dvObject])^ := vt;
|
|
assert({%H-}SynVariantTypes[0].VarType = vt);
|
|
PDocVariantData(@DV_FAST[dvUndefined])^.VOptions := JSON_FAST;
|
|
PDocVariantData(@DV_FAST[dvArray])^.VOptions := JSON_FAST + [dvoIsArray];
|
|
PDocVariantData(@DV_FAST[dvObject])^.VOptions := JSON_FAST + [dvoIsObject];
|
|
// FPC allows to define variables with absolute JSON_[...] but Delphi doesn't
|
|
JSON_FAST_STRICT := JSON_[mFastStrict];
|
|
JSON_FAST_EXTENDED := JSON_[mFastExtended];
|
|
JSON_FAST_EXTENDEDINTERN := JSON_[mFastExtendedIntern];
|
|
JSON_NAMEVALUE := PDocVariantOptionsBool(@JSON_[mNameValue])^;
|
|
JSON_NAMEVALUEINTERN := PDocVariantOptionsBool(@JSON_[mNameValueIntern])^;
|
|
JSON_OPTIONS := PDocVariantOptionsBool(@JSON_[mDefault])^;
|
|
// redirect to the feature complete variant wrapper functions
|
|
BinaryVariantLoadAsJson := _BinaryVariantLoadAsJson;
|
|
VariantClearSeveral := _VariantClearSeveral;
|
|
_VariantSaveJson := @__VariantSaveJson;
|
|
SortDynArrayVariantComp := pointer(@FastVarDataComp);
|
|
// setup FastVarDataComp() efficient lookup comparison functions
|
|
for ins := false to true do
|
|
begin
|
|
for i := low(_VARDATACMPNUM1) to high(_VARDATACMPNUM1) do
|
|
_VARDATACMP[i, ins] := _VARDATACMPNUM1[i];
|
|
_VARDATACMP[varBoolean, ins] := 14;
|
|
for i := low(_VARDATACMPNUM2) to high(_VARDATACMPNUM2) do
|
|
_VARDATACMP[i, ins] := _VARDATACMPNUM2[i];
|
|
end;
|
|
_VARDATACMP[varString, false] := 15;
|
|
_VARDATACMP[varString, true] := 16;
|
|
_VARDATACMP[varOleStr, false] := 17;
|
|
_VARDATACMP[varOleStr, true] := 18;
|
|
{$ifdef HASVARUSTRING}
|
|
_VARDATACMP[varUString, false] := 17;
|
|
_VARDATACMP[varUString, true] := 18;
|
|
{$endif HASVARUSTRING}
|
|
// patch DispInvoke for performance and to circumvent RTL inconsistencies
|
|
GetVariantManager(vm);
|
|
vm.DispInvoke := NewDispInvoke;
|
|
SetVariantManager(vm);
|
|
{$ifdef FPC}
|
|
// circumvent FPC 3.2+ inverted parameters order - may be fixed in later FPC
|
|
test := _ObjFast([]);
|
|
try
|
|
test.Add('nam', 'val'); // late binding DispInvoke() call
|
|
DispInvokeArgOrderInverted := (_Safe(test)^.Names[0] = 'val');
|
|
except // paranoid to avoid fatal exception during process initialization
|
|
end;
|
|
{$endif FPC}
|
|
end;
|
|
|
|
|
|
initialization
|
|
InitializeUnit;
|
|
|
|
end.
|
|
|