Types-JSON
This commit is contained in:
@@ -0,0 +1,258 @@
|
|||||||
|
unit Myc.Data.Types.JSON;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.SysUtils,
|
||||||
|
System.JSON,
|
||||||
|
Myc.Data.Types;
|
||||||
|
|
||||||
|
type
|
||||||
|
// Provides methods for serializing and deserializing data values to/from JSON using the TDataType helpers.
|
||||||
|
TDataJsonConverter = class
|
||||||
|
public
|
||||||
|
// Serializes a data value to a TJSONValue.
|
||||||
|
class function ValueToJson(const Value: TDataType.TValue): TJSONValue;
|
||||||
|
|
||||||
|
// Deserializes a TJSONValue to a data value based on the provided data type.
|
||||||
|
class function JsonToValue(const Json: TJSONValue; const DataType: TDataType): TDataType.TValue;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.Rtti,
|
||||||
|
System.TypInfo,
|
||||||
|
System.DateUtils,
|
||||||
|
System.Math;
|
||||||
|
|
||||||
|
{ TDataJsonConverter }
|
||||||
|
|
||||||
|
class function TDataJsonConverter.ValueToJson(const Value: TDataType.TValue): TJSONValue;
|
||||||
|
var
|
||||||
|
jsonObject: TJSONObject;
|
||||||
|
jsonArray: TJSONArray;
|
||||||
|
i: Integer;
|
||||||
|
begin
|
||||||
|
if not Assigned(Value.DataValue) then
|
||||||
|
begin
|
||||||
|
Result := TJSONNull.Create;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
case Value.DataType.Kind of
|
||||||
|
dkVoid: Result := TJSONNull.Create;
|
||||||
|
dkOrdinal: Result := TJSONNumber.Create(Value.AsOrdinal.Value);
|
||||||
|
dkFloat: Result := TJSONNumber.Create(Value.AsFloat.Value);
|
||||||
|
dkText: Result := TJSONString.Create(Value.AsText.Value);
|
||||||
|
dkTimestamp: Result := TJSONString.Create(DateToISO8601(Value.AsTimestamp.Value, true));
|
||||||
|
dkDecimal:
|
||||||
|
begin
|
||||||
|
// Serialize as string to preserve precision.
|
||||||
|
var decValue := Value.AsDecimal;
|
||||||
|
var val := decValue.Value;
|
||||||
|
var scale := decValue.DataType.Scale;
|
||||||
|
var s: string;
|
||||||
|
|
||||||
|
if (scale = 0) then
|
||||||
|
s := IntToStr(val)
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
s := IntToStr(Abs(val));
|
||||||
|
if (Length(s) <= scale) then
|
||||||
|
s := StringOfChar('0', scale - Length(s) + 1) + s;
|
||||||
|
|
||||||
|
s := s.Insert(Length(s) - scale, '.');
|
||||||
|
|
||||||
|
if (val < 0) then
|
||||||
|
s := '-' + s;
|
||||||
|
end;
|
||||||
|
Result := TJSONString.Create(s);
|
||||||
|
end;
|
||||||
|
dkEnum:
|
||||||
|
begin
|
||||||
|
var enumValue := Value.AsEnum;
|
||||||
|
Result := TJSONString.Create(enumValue.DataType.Identifiers[enumValue.Value]);
|
||||||
|
end;
|
||||||
|
dkRecord:
|
||||||
|
begin
|
||||||
|
var recordValue := Value.AsRecord;
|
||||||
|
var recordType := recordValue.DataType;
|
||||||
|
jsonObject := TJSONObject.Create;
|
||||||
|
Result := jsonObject;
|
||||||
|
for i := 0 to recordType.FieldCount - 1 do
|
||||||
|
begin
|
||||||
|
jsonObject.AddPair(
|
||||||
|
recordType.Fields[i].Name,
|
||||||
|
ValueToJson(recordValue.Items[i]) // Items property returns TDataType.TValue
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
dkTuple:
|
||||||
|
begin
|
||||||
|
var tupleValue := Value.AsTuple;
|
||||||
|
jsonArray := TJSONArray.Create;
|
||||||
|
Result := jsonArray;
|
||||||
|
for i := 0 to tupleValue.ItemCount - 1 do
|
||||||
|
jsonArray.AddElement(ValueToJson(tupleValue.Items[i]));
|
||||||
|
end;
|
||||||
|
dkArray:
|
||||||
|
begin
|
||||||
|
var arrayValue := Value.AsArray;
|
||||||
|
jsonArray := TJSONArray.Create;
|
||||||
|
Result := jsonArray;
|
||||||
|
for i := 0 to arrayValue.ElementCount - 1 do
|
||||||
|
jsonArray.AddElement(ValueToJson(arrayValue.Items[i]));
|
||||||
|
end;
|
||||||
|
dkVector:
|
||||||
|
begin
|
||||||
|
var vectorValue := Value.AsVector;
|
||||||
|
jsonArray := TJSONArray.Create;
|
||||||
|
Result := jsonArray;
|
||||||
|
for i := 0 to vectorValue.ElementCount - 1 do
|
||||||
|
jsonArray.AddElement(ValueToJson(vectorValue.Items[i]));
|
||||||
|
end;
|
||||||
|
dkMethod: raise ENotSupportedException.Create('Cannot serialize a method type to JSON.');
|
||||||
|
else
|
||||||
|
raise ENotSupportedException.CreateFmt('Unsupported data kind: %s', [GetEnumName(TypeInfo(TDataKind), Ord(Value.DataType.Kind))]);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
class function TDataJsonConverter.JsonToValue(const Json: TJSONValue; const DataType: TDataType): TDataType.TValue;
|
||||||
|
var
|
||||||
|
jsonObject: TJSONObject;
|
||||||
|
jsonArray: TJSONArray;
|
||||||
|
items: TArray<IDataValue>;
|
||||||
|
i, idx: Integer;
|
||||||
|
val: Int64;
|
||||||
|
begin
|
||||||
|
Assert(Assigned(DataType.DataType), 'DataType cannot be nil for deserialization');
|
||||||
|
|
||||||
|
case DataType.Kind of
|
||||||
|
dkVoid:
|
||||||
|
if (Json is TJSONNull) then
|
||||||
|
Result := TDataType.Void.Value
|
||||||
|
else
|
||||||
|
raise EConvertError.Create('JSON Null expected for Void type');
|
||||||
|
dkOrdinal: Result := DataType.AsOrdinal.CreateValue((Json as TJSONNumber).AsInt64);
|
||||||
|
dkFloat: Result := DataType.AsFloat.CreateValue((Json as TJSONNumber).AsDouble);
|
||||||
|
dkText: Result := DataType.AsText.CreateValue((Json as TJSONString).Value);
|
||||||
|
dkTimestamp: Result := DataType.AsTimestamp.CreateValue(ISO8601ToDate((Json as TJSONString).Value, True));
|
||||||
|
dkDecimal:
|
||||||
|
begin
|
||||||
|
// Handle string representation for precision, with fallback to number for compatibility.
|
||||||
|
var decimalType := DataType.AsDecimal;
|
||||||
|
if (Json is TJSONString) then
|
||||||
|
begin
|
||||||
|
var s := (Json as TJSONString).Value.Trim;
|
||||||
|
var isNegative := s.StartsWith('-');
|
||||||
|
if isNegative then
|
||||||
|
s := s.Remove(0, 1); // remove sign
|
||||||
|
|
||||||
|
var dotPos := Pos('.', s);
|
||||||
|
var numFracDigits: Integer;
|
||||||
|
if (dotPos = 0) then
|
||||||
|
numFracDigits := 0
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
numFracDigits := Length(s) - dotPos;
|
||||||
|
s := s.Remove(dotPos - 1, 1); // remove dot
|
||||||
|
end;
|
||||||
|
|
||||||
|
val := StrToInt64(s);
|
||||||
|
|
||||||
|
var scaleDiff := decimalType.Scale - numFracDigits;
|
||||||
|
if (scaleDiff > 0) then // Not enough fractional digits
|
||||||
|
begin
|
||||||
|
for i := 1 to scaleDiff do
|
||||||
|
val := val * 10;
|
||||||
|
end
|
||||||
|
else if (scaleDiff < 0) then // Too many fractional digits
|
||||||
|
begin
|
||||||
|
for i := 1 to -scaleDiff do
|
||||||
|
val := val div 10;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if isNegative then
|
||||||
|
val := -val;
|
||||||
|
|
||||||
|
Result := decimalType.CreateValue(val);
|
||||||
|
end
|
||||||
|
else if (Json is TJSONNumber) then
|
||||||
|
begin
|
||||||
|
// Support numbers for compatibility, but this can lead to precision loss.
|
||||||
|
val := Round((Json as TJSONNumber).AsDouble * Power(10, decimalType.Scale));
|
||||||
|
Result := decimalType.CreateValue(val);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise EConvertError.Create('JSON String or Number expected for Decimal type');
|
||||||
|
end;
|
||||||
|
dkEnum:
|
||||||
|
begin
|
||||||
|
var enumType := DataType.AsEnum;
|
||||||
|
idx := enumType.IndexOf((Json as TJSONString).Value);
|
||||||
|
if (idx < 0) then
|
||||||
|
raise EConvertError.CreateFmt('Invalid enum identifier "%s" for type %s', [Json.Value, DataType.Name]);
|
||||||
|
Result := enumType.CreateValue(idx);
|
||||||
|
end;
|
||||||
|
dkRecord:
|
||||||
|
begin
|
||||||
|
var recordType := DataType.AsRecord;
|
||||||
|
jsonObject := Json as TJSONObject;
|
||||||
|
SetLength(items, recordType.FieldCount);
|
||||||
|
for i := 0 to recordType.FieldCount - 1 do
|
||||||
|
begin
|
||||||
|
var field := recordType.Fields[i];
|
||||||
|
var jsonPair := jsonObject.Get(field.Name);
|
||||||
|
if not Assigned(jsonPair) then
|
||||||
|
raise EConvertError.CreateFmt('Missing field "%s" for record type %s', [field.Name, DataType.Name]);
|
||||||
|
|
||||||
|
// Recursive call returns TDataType.TValue, which implicitly converts to IDataValue for the array
|
||||||
|
items[i] := JsonToValue(jsonPair.JsonValue, field.DataType);
|
||||||
|
end;
|
||||||
|
Result := recordType.CreateValue(items);
|
||||||
|
end;
|
||||||
|
dkTuple:
|
||||||
|
begin
|
||||||
|
var tupleType := DataType.AsTuple;
|
||||||
|
jsonArray := Json as TJSONArray;
|
||||||
|
if (jsonArray.Count <> IDataTupleType(tupleType).ItemCount) then
|
||||||
|
raise EConvertError
|
||||||
|
.CreateFmt('JSON array size (%d) does not match tuple size (%d)', [jsonArray.Count, tupleType.ItemCount]);
|
||||||
|
|
||||||
|
SetLength(items, IDataTupleType(tupleType).ItemCount);
|
||||||
|
for i := 0 to IDataTupleType(tupleType).ItemCount - 1 do
|
||||||
|
begin
|
||||||
|
var itemType := IDataTupleType(tupleType).ItemType[i];
|
||||||
|
items[i] := JsonToValue(jsonArray.Items[i], itemType);
|
||||||
|
end;
|
||||||
|
Result := tupleType.CreateValue(items);
|
||||||
|
end;
|
||||||
|
dkArray:
|
||||||
|
begin
|
||||||
|
var arrayType := DataType.AsArray;
|
||||||
|
jsonArray := Json as TJSONArray;
|
||||||
|
SetLength(items, jsonArray.Count);
|
||||||
|
for i := 0 to jsonArray.Count - 1 do
|
||||||
|
items[i] := JsonToValue(jsonArray.Items[i], arrayType.ElementType);
|
||||||
|
Result := arrayType.CreateValue(items);
|
||||||
|
end;
|
||||||
|
dkVector:
|
||||||
|
begin
|
||||||
|
var vectorType := DataType.AsVector;
|
||||||
|
jsonArray := Json as TJSONArray;
|
||||||
|
if (jsonArray.Count <> vectorType.ElementCount) then
|
||||||
|
raise EConvertError
|
||||||
|
.CreateFmt('JSON array size (%d) does not match vector size (%d)', [jsonArray.Count, vectorType.ElementCount]);
|
||||||
|
SetLength(items, jsonArray.Count);
|
||||||
|
for i := 0 to jsonArray.Count - 1 do
|
||||||
|
items[i] := JsonToValue(jsonArray.Items[i], vectorType.ElementType);
|
||||||
|
Result := vectorType.CreateValue(items);
|
||||||
|
end;
|
||||||
|
dkMethod: raise ENotSupportedException.Create('Cannot deserialize a method type from JSON.');
|
||||||
|
else
|
||||||
|
raise ENotSupportedException.CreateFmt('Unsupported data kind: %s', [GetEnumName(TypeInfo(TDataKind), Ord(DataType.Kind))]);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
@@ -7,10 +7,7 @@ uses
|
|||||||
System.RTTI,
|
System.RTTI,
|
||||||
Myc.Data.Types;
|
Myc.Data.Types;
|
||||||
|
|
||||||
type
|
function AsTValue(const Value: TDataType.TValue): TValue;
|
||||||
TValueHelper = record helper for TDataType.TValue
|
|
||||||
function AsTValue: TValue;
|
|
||||||
end;
|
|
||||||
|
|
||||||
function RttiTypeToDataType(rttiType: TRttiType): TDataType;
|
function RttiTypeToDataType(rttiType: TRttiType): TDataType;
|
||||||
|
|
||||||
@@ -69,16 +66,16 @@ end;
|
|||||||
|
|
||||||
{ TValueHelper }
|
{ TValueHelper }
|
||||||
|
|
||||||
function TValueHelper.AsTValue: TValue;
|
function AsTValue(const Value: TDataType.TValue): TValue;
|
||||||
begin
|
begin
|
||||||
case DataType.Kind of
|
case Value.DataType.Kind of
|
||||||
dkVoid: Result := TValue.Empty;
|
dkVoid: Result := TValue.Empty;
|
||||||
dkOrdinal: Result := TValue.From<Int64>(AsOrdinal.Value);
|
dkOrdinal: Result := TValue.From<Int64>(Value.AsOrdinal.Value);
|
||||||
dkFloat: Result := TValue.From<Double>(AsFloat.Value);
|
dkFloat: Result := TValue.From<Double>(Value.AsFloat.Value);
|
||||||
dkText: Result := TValue.From<string>(AsText.Value);
|
dkText: Result := TValue.From<string>(Value.AsText.Value);
|
||||||
dkTimestamp: Result := TValue.From<TDateTime>(AsTimestamp.Value);
|
dkTimestamp: Result := TValue.From<TDateTime>(Value.AsTimestamp.Value);
|
||||||
dkEnum: Result := TValue.From<Integer>(AsEnum.Value);
|
dkEnum: Result := TValue.From<Integer>(Value.AsEnum.Value);
|
||||||
dkMethod: Result := TValue.From<TDataMethodProc>(AsMethod.Value);
|
dkMethod: Result := TValue.From<TDataMethodProc>(Value.AsMethod.Value);
|
||||||
else
|
else
|
||||||
raise EInvalidOpException.Create('Unsupported data type');
|
raise EInvalidOpException.Create('Unsupported data type');
|
||||||
end;
|
end;
|
||||||
|
|||||||
+123
-98
@@ -169,7 +169,13 @@ type
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
IDataTupleType = interface(IDataType)
|
IDataTupleType = interface(IDataType)
|
||||||
|
{$region 'private'}
|
||||||
|
function GetItemCount: Integer;
|
||||||
|
function GetItemType(Idx: Integer): IDataType;
|
||||||
|
{$endregion}
|
||||||
function CreateValue(const AItems: array of IDataValue): IDataTupleValue;
|
function CreateValue(const AItems: array of IDataValue): IDataTupleValue;
|
||||||
|
property ItemCount: Integer read GetItemCount;
|
||||||
|
property ItemType[Idx: Integer]: IDataType read GetItemType;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
IDataArrayValue = interface(IDataValue)
|
IDataArrayValue = interface(IDataValue)
|
||||||
@@ -219,20 +225,6 @@ type
|
|||||||
public
|
public
|
||||||
constructor Create(const ADataValue: IDataValue);
|
constructor Create(const ADataValue: IDataValue);
|
||||||
|
|
||||||
// Casting
|
|
||||||
function AsOrdinal: IDataOrdinalValue; inline;
|
|
||||||
function AsFloat: IDataFloatValue; inline;
|
|
||||||
function AsText: IDataTextValue; inline;
|
|
||||||
function AsTimestamp: IDataTimestampValue; inline;
|
|
||||||
function AsDecimal: IDataDecimalValue; inline;
|
|
||||||
function AsRecord: IDataRecordValue; inline;
|
|
||||||
function AsTuple: IDataTupleValue; inline;
|
|
||||||
function AsArray: IDataArrayValue; inline;
|
|
||||||
function AsVector: IDataVectorValue; inline;
|
|
||||||
function AsEnum: IDataEnumValue; inline;
|
|
||||||
function AsMethod: IDataMethodValue; inline;
|
|
||||||
function AsVoid: IDataVoidValue; inline;
|
|
||||||
|
|
||||||
class operator Implicit(const A: IDataValue): TDataType.TValue; overload; inline;
|
class operator Implicit(const A: IDataValue): TDataType.TValue; overload; inline;
|
||||||
class operator Implicit(const A: TDataType.TValue): IDataValue; overload; inline;
|
class operator Implicit(const A: TDataType.TValue): IDataValue; overload; inline;
|
||||||
|
|
||||||
@@ -466,11 +458,15 @@ type
|
|||||||
end;
|
end;
|
||||||
private
|
private
|
||||||
FTupleType: IDataTupleType;
|
FTupleType: IDataTupleType;
|
||||||
|
function GetItemCount: Integer; inline;
|
||||||
|
function GetItemType(Idx: Integer): IDataType; inline;
|
||||||
public
|
public
|
||||||
constructor Create(const ATupleType: IDataTupleType);
|
constructor Create(const ATupleType: IDataTupleType);
|
||||||
class operator Implicit(const A: IDataTupleType): TDataType.TTuple; overload; inline;
|
class operator Implicit(const A: IDataTupleType): TDataType.TTuple; overload; inline;
|
||||||
class operator Implicit(const A: TDataType.TTuple): IDataTupleType; overload; inline;
|
class operator Implicit(const A: TDataType.TTuple): IDataTupleType; overload; inline;
|
||||||
function CreateValue(const AItems: array of IDataValue): TDataType.TTuple.TValue;
|
function CreateValue(const AItems: array of IDataValue): TDataType.TTuple.TValue;
|
||||||
|
property ItemCount: Integer read GetItemCount;
|
||||||
|
property ItemType[Idx: Integer]: IDataType read GetItemType;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TArray = record
|
TArray = record
|
||||||
@@ -560,6 +556,23 @@ type
|
|||||||
function CreateValue(const Proc: TProc): TDataType.TMethod.TValue; inline;
|
function CreateValue(const Proc: TProc): TDataType.TMethod.TValue; inline;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// Helper to cast a generic TValue to a specific value helper.
|
||||||
|
TValueHelper = record helper for TValue
|
||||||
|
public
|
||||||
|
function AsVoid: TVoid.TValue; inline;
|
||||||
|
function AsOrdinal: TOrdinal.TValue; inline;
|
||||||
|
function AsFloat: TFloat.TValue; inline;
|
||||||
|
function AsText: TText.TValue; inline;
|
||||||
|
function AsTimestamp: TTimestamp.TValue; inline;
|
||||||
|
function AsDecimal: TDecimal.TValue; inline;
|
||||||
|
function AsRecord: TRecord.TValue; inline;
|
||||||
|
function AsTuple: TTuple.TValue; inline;
|
||||||
|
function AsArray: TArray.TValue; inline;
|
||||||
|
function AsVector: TVector.TValue; inline;
|
||||||
|
function AsEnum: TEnum.TValue; inline;
|
||||||
|
function AsMethod: TMethod.TValue; inline;
|
||||||
|
end;
|
||||||
|
|
||||||
strict private
|
strict private
|
||||||
FDataType: IDataType;
|
FDataType: IDataType;
|
||||||
function GetName: String; inline;
|
function GetName: String; inline;
|
||||||
@@ -1247,6 +1260,16 @@ begin
|
|||||||
Result.Create(FTupleType.CreateValue(AItems));
|
Result.Create(FTupleType.CreateValue(AItems));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TDataType.TTuple.GetItemCount: Integer;
|
||||||
|
begin
|
||||||
|
Result := FTupleType.ItemCount;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDataType.TTuple.GetItemType(Idx: Integer): IDataType;
|
||||||
|
begin
|
||||||
|
Result := FTupleType.ItemType[Idx];
|
||||||
|
end;
|
||||||
|
|
||||||
class operator TDataType.TTuple.Implicit(const A: IDataTupleType): TDataType.TTuple;
|
class operator TDataType.TTuple.Implicit(const A: IDataTupleType): TDataType.TTuple;
|
||||||
begin
|
begin
|
||||||
Result.FTupleType := A;
|
Result.FTupleType := A;
|
||||||
@@ -1590,90 +1613,6 @@ begin
|
|||||||
FDataValue := ADataValue;
|
FDataValue := ADataValue;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TDataType.TValue.AsArray: IDataArrayValue;
|
|
||||||
begin
|
|
||||||
if (FDataValue.DataType.Kind <> dkArray) then
|
|
||||||
raise EInvalidCast.Create('Array expected');
|
|
||||||
Result := IDataArrayValue(FDataValue);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TDataType.TValue.AsDecimal: IDataDecimalValue;
|
|
||||||
begin
|
|
||||||
if (FDataValue.DataType.Kind <> dkDecimal) then
|
|
||||||
raise EInvalidCast.Create('Decimal expected');
|
|
||||||
Result := IDataDecimalValue(FDataValue);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TDataType.TValue.AsEnum: IDataEnumValue;
|
|
||||||
begin
|
|
||||||
if (FDataValue.DataType.Kind <> dkEnum) then
|
|
||||||
raise EInvalidCast.Create('Enum expected');
|
|
||||||
Result := IDataEnumValue(FDataValue);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TDataType.TValue.AsFloat: IDataFloatValue;
|
|
||||||
begin
|
|
||||||
if (FDataValue.DataType.Kind <> dkFloat) then
|
|
||||||
raise EInvalidCast.Create('Float expected');
|
|
||||||
Result := IDataFloatValue(FDataValue);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TDataType.TValue.AsMethod: IDataMethodValue;
|
|
||||||
begin
|
|
||||||
if (FDataValue.DataType.Kind <> dkMethod) then
|
|
||||||
raise EInvalidCast.Create('Method expected');
|
|
||||||
Result := IDataMethodValue(FDataValue);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TDataType.TValue.AsOrdinal: IDataOrdinalValue;
|
|
||||||
begin
|
|
||||||
if (FDataValue.DataType.Kind <> dkOrdinal) then
|
|
||||||
raise EInvalidCast.Create('Ordinal expected');
|
|
||||||
Result := IDataOrdinalValue(FDataValue);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TDataType.TValue.AsRecord: IDataRecordValue;
|
|
||||||
begin
|
|
||||||
if (FDataValue.DataType.Kind <> dkRecord) then
|
|
||||||
raise EInvalidCast.Create('Record expected');
|
|
||||||
Result := IDataRecordValue(FDataValue);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TDataType.TValue.AsText: IDataTextValue;
|
|
||||||
begin
|
|
||||||
if (FDataValue.DataType.Kind <> dkText) then
|
|
||||||
raise EInvalidCast.Create('Text expected');
|
|
||||||
Result := IDataTextValue(FDataValue);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TDataType.TValue.AsTimestamp: IDataTimestampValue;
|
|
||||||
begin
|
|
||||||
if (FDataValue.DataType.Kind <> dkTimestamp) then
|
|
||||||
raise EInvalidCast.Create('Timestamp expected');
|
|
||||||
Result := IDataTimestampValue(FDataValue);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TDataType.TValue.AsTuple: IDataTupleValue;
|
|
||||||
begin
|
|
||||||
if (FDataValue.DataType.Kind <> dkTuple) then
|
|
||||||
raise EInvalidCast.Create('Tuple expected');
|
|
||||||
Result := IDataTupleValue(FDataValue);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TDataType.TValue.AsVector: IDataVectorValue;
|
|
||||||
begin
|
|
||||||
if (FDataValue.DataType.Kind <> dkVector) then
|
|
||||||
raise EInvalidCast.Create('Vector expected');
|
|
||||||
Result := IDataVectorValue(FDataValue);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TDataType.TValue.AsVoid: IDataVoidValue;
|
|
||||||
begin
|
|
||||||
if (FDataValue.DataType.Kind <> dkVoid) then
|
|
||||||
raise EInvalidCast.Create('Void expected');
|
|
||||||
Result := IDataVoidValue(FDataValue);
|
|
||||||
end;
|
|
||||||
|
|
||||||
function TDataType.TValue.GetDataType: IDataType;
|
function TDataType.TValue.GetDataType: IDataType;
|
||||||
begin
|
begin
|
||||||
if Assigned(FDataValue) then
|
if Assigned(FDataValue) then
|
||||||
@@ -1769,4 +1708,90 @@ begin
|
|||||||
Result.Create(IDataVectorType(FDataType));
|
Result.Create(IDataVectorType(FDataType));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ TDataType.TValueHelper }
|
||||||
|
|
||||||
|
function TDataType.TValueHelper.AsArray: TDataType.TArray.TValue;
|
||||||
|
begin
|
||||||
|
if (FDataValue.DataType.Kind <> dkArray) then
|
||||||
|
raise EInvalidCast.Create('Array expected');
|
||||||
|
Result.Create(IDataArrayValue(FDataValue));
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDataType.TValueHelper.AsDecimal: TDecimal.TValue;
|
||||||
|
begin
|
||||||
|
if (FDataValue.DataType.Kind <> dkDecimal) then
|
||||||
|
raise EInvalidCast.Create('Decimal expected');
|
||||||
|
Result.Create(IDataDecimalValue(FDataValue));
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDataType.TValueHelper.AsEnum: TDataType.TEnum.TValue;
|
||||||
|
begin
|
||||||
|
if (FDataValue.DataType.Kind <> dkEnum) then
|
||||||
|
raise EInvalidCast.Create('Enum expected');
|
||||||
|
Result.Create(IDataEnumValue(FDataValue));
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDataType.TValueHelper.AsFloat: TDataType.TFloat.TValue;
|
||||||
|
begin
|
||||||
|
if (FDataValue.DataType.Kind <> dkFloat) then
|
||||||
|
raise EInvalidCast.Create('Float expected');
|
||||||
|
Result.Create(IDataFloatValue(FDataValue));
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDataType.TValueHelper.AsMethod: TDataType.TMethod.TValue;
|
||||||
|
begin
|
||||||
|
if (FDataValue.DataType.Kind <> dkMethod) then
|
||||||
|
raise EInvalidCast.Create('Method expected');
|
||||||
|
Result.Create(IDataMethodValue(FDataValue));
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDataType.TValueHelper.AsOrdinal: TDataType.TOrdinal.TValue;
|
||||||
|
begin
|
||||||
|
if (FDataValue.DataType.Kind <> dkOrdinal) then
|
||||||
|
raise EInvalidCast.Create('Ordinal expected');
|
||||||
|
Result.Create(IDataOrdinalValue(FDataValue));
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDataType.TValueHelper.AsRecord: TDataType.TRecord.TValue;
|
||||||
|
begin
|
||||||
|
if (FDataValue.DataType.Kind <> dkRecord) then
|
||||||
|
raise EInvalidCast.Create('Record expected');
|
||||||
|
Result.Create(IDataRecordValue(FDataValue));
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDataType.TValueHelper.AsText: TDataType.TText.TValue;
|
||||||
|
begin
|
||||||
|
if (FDataValue.DataType.Kind <> dkText) then
|
||||||
|
raise EInvalidCast.Create('Text expected');
|
||||||
|
Result.Create(IDataTextValue(FDataValue));
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDataType.TValueHelper.AsTimestamp: TDataType.TTimestamp.TValue;
|
||||||
|
begin
|
||||||
|
if (FDataValue.DataType.Kind <> dkTimestamp) then
|
||||||
|
raise EInvalidCast.Create('Timestamp expected');
|
||||||
|
Result.Create(IDataTimestampValue(FDataValue));
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDataType.TValueHelper.AsTuple: TDataType.TTuple.TValue;
|
||||||
|
begin
|
||||||
|
if (FDataValue.DataType.Kind <> dkTuple) then
|
||||||
|
raise EInvalidCast.Create('Tuple expected');
|
||||||
|
Result.Create(IDataTupleValue(FDataValue));
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDataType.TValueHelper.AsVector: TDataType.TVector.TValue;
|
||||||
|
begin
|
||||||
|
if (FDataValue.DataType.Kind <> dkVector) then
|
||||||
|
raise EInvalidCast.Create('Vector expected');
|
||||||
|
Result.Create(IDataVectorValue(FDataValue));
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TDataType.TValueHelper.AsVoid: TDataType.TVoid.TValue;
|
||||||
|
begin
|
||||||
|
if (FDataValue.DataType.Kind <> dkVoid) then
|
||||||
|
raise EInvalidCast.Create('Void expected');
|
||||||
|
Result.Create(IDataVoidValue(FDataValue));
|
||||||
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -0,0 +1,412 @@
|
|||||||
|
unit TestDataTypes.JSON;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.JSON,
|
||||||
|
DUnitX.TestFramework;
|
||||||
|
|
||||||
|
type
|
||||||
|
[TestFixture]
|
||||||
|
[IgnoreMemoryLeaks]
|
||||||
|
TTestDataTypesJSON = class
|
||||||
|
private
|
||||||
|
FJson: TJSONValue;
|
||||||
|
public
|
||||||
|
[Setup]
|
||||||
|
procedure SetUp;
|
||||||
|
[TearDown]
|
||||||
|
procedure TearDown;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure TestVoid;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCase('Positive', '12345')]
|
||||||
|
[TestCase('Negative', '-54321')]
|
||||||
|
[TestCase('Zero', '0')]
|
||||||
|
procedure TestOrdinal(const AValue: Int64);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCase('Positive', '123.456')]
|
||||||
|
[TestCase('Negative', '-543.21')]
|
||||||
|
[TestCase('Zero', '0.0')]
|
||||||
|
procedure TestFloat(const AValue: Double);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCase('Simple', 'Hello World')]
|
||||||
|
[TestCase('Empty', '')]
|
||||||
|
[TestCase('SpecialChars', 'äöüß#+*?%&/()=')]
|
||||||
|
procedure TestText(const AValue: string);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure TestTimestamp;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCase('Scale4_Positive', '1234567,4,123.4567')]
|
||||||
|
[TestCase('Scale2_Negative', '-987,2,-9.87')]
|
||||||
|
[TestCase('Scale5_Small', '123,5,0.00123')]
|
||||||
|
[TestCase('Scale0_Int', '123,0,123')]
|
||||||
|
[TestCase('Scale2_Zero', '0,2,0.00')]
|
||||||
|
[TestCase('Scale0_Zero', '0,0,0')]
|
||||||
|
procedure TestDecimal(const AValue: Int64; AScale: Integer; const AExpectedJsonString: string);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure TestEnum;
|
||||||
|
[Test]
|
||||||
|
procedure TestRecord;
|
||||||
|
[Test]
|
||||||
|
procedure TestTuple;
|
||||||
|
[Test]
|
||||||
|
procedure TestArray;
|
||||||
|
[Test]
|
||||||
|
procedure TestVector;
|
||||||
|
[Test]
|
||||||
|
procedure TestMethodFails;
|
||||||
|
[Test]
|
||||||
|
procedure TestNullValue;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.SysUtils,
|
||||||
|
System.DateUtils,
|
||||||
|
System.Math,
|
||||||
|
Myc.Data.Types,
|
||||||
|
Myc.Data.Types.JSON;
|
||||||
|
|
||||||
|
{ TTestDataTypesJSON }
|
||||||
|
|
||||||
|
procedure TTestDataTypesJSON.SetUp;
|
||||||
|
begin
|
||||||
|
FJson := nil;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestDataTypesJSON.TearDown;
|
||||||
|
begin
|
||||||
|
FJson.Free;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestDataTypesJSON.TestVoid;
|
||||||
|
var
|
||||||
|
voidType: TDataType.TVoid;
|
||||||
|
voidValue: TDataType.TVoid.TValue;
|
||||||
|
deserializedValue: TDataType.TValue;
|
||||||
|
begin
|
||||||
|
// Setup
|
||||||
|
voidType := TDataType.Void;
|
||||||
|
voidValue := voidType.Value;
|
||||||
|
|
||||||
|
// Serialize
|
||||||
|
FJson := TDataJsonConverter.ValueToJson(voidValue);
|
||||||
|
Assert.IsNotNull(FJson, 'JSON value should not be nil');
|
||||||
|
Assert.IsTrue(FJson is TJSONNull, 'Void should serialize to JSON Null');
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
deserializedValue := TDataJsonConverter.JsonToValue(FJson, voidType);
|
||||||
|
Assert.IsNotNull(deserializedValue.DataValue, 'Deserialized value should not be nil');
|
||||||
|
Assert.AreEqual(dkVoid, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkVoid');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestDataTypesJSON.TestOrdinal(const AValue: Int64);
|
||||||
|
var
|
||||||
|
ordType: TDataType.TOrdinal;
|
||||||
|
ordValue: TDataType.TOrdinal.TValue;
|
||||||
|
deserializedValue: TDataType.TValue;
|
||||||
|
begin
|
||||||
|
// Setup
|
||||||
|
ordType := TDataType.Ordinal;
|
||||||
|
ordValue := ordType.CreateValue(AValue);
|
||||||
|
|
||||||
|
// Serialize
|
||||||
|
FJson := TDataJsonConverter.ValueToJson(ordValue);
|
||||||
|
Assert.IsTrue(FJson is TJSONNumber, 'Ordinal should serialize to JSON Number');
|
||||||
|
Assert.AreEqual(AValue, (FJson as TJSONNumber).AsInt64, 'Serialized value mismatch');
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
deserializedValue := TDataJsonConverter.JsonToValue(FJson, ordType);
|
||||||
|
Assert.AreEqual(dkOrdinal, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkOrdinal');
|
||||||
|
Assert.AreEqual(AValue, deserializedValue.AsOrdinal.Value, 'Deserialized value mismatch');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestDataTypesJSON.TestFloat(const AValue: Double);
|
||||||
|
var
|
||||||
|
floatType: TDataType.TFloat;
|
||||||
|
floatValue: TDataType.TFloat.TValue;
|
||||||
|
deserializedValue: TDataType.TValue;
|
||||||
|
begin
|
||||||
|
// Setup
|
||||||
|
floatType := TDataType.Float;
|
||||||
|
floatValue := floatType.CreateValue(AValue);
|
||||||
|
|
||||||
|
// Serialize
|
||||||
|
FJson := TDataJsonConverter.ValueToJson(floatValue);
|
||||||
|
Assert.IsTrue(FJson is TJSONNumber, 'Float should serialize to JSON Number');
|
||||||
|
Assert.AreEqual(AValue, (FJson as TJSONNumber).AsDouble, 1E-9, 'Serialized value mismatch');
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
deserializedValue := TDataJsonConverter.JsonToValue(FJson, floatType);
|
||||||
|
Assert.AreEqual(dkFloat, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkFloat');
|
||||||
|
Assert.AreEqual(AValue, deserializedValue.AsFloat.Value, 1E-9, 'Deserialized value mismatch');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestDataTypesJSON.TestText(const AValue: string);
|
||||||
|
var
|
||||||
|
textType: TDataType.TText;
|
||||||
|
textValue: TDataType.TText.TValue;
|
||||||
|
deserializedValue: TDataType.TValue;
|
||||||
|
begin
|
||||||
|
// Setup
|
||||||
|
textType := TDataType.Text;
|
||||||
|
textValue := textType.CreateValue(AValue);
|
||||||
|
|
||||||
|
// Serialize
|
||||||
|
FJson := TDataJsonConverter.ValueToJson(textValue);
|
||||||
|
Assert.IsTrue(FJson is TJSONString, 'Text should serialize to JSON String');
|
||||||
|
Assert.AreEqual(AValue, (FJson as TJSONString).Value, 'Serialized value mismatch');
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
deserializedValue := TDataJsonConverter.JsonToValue(FJson, textType);
|
||||||
|
Assert.AreEqual(dkText, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkText');
|
||||||
|
Assert.AreEqual(AValue, deserializedValue.AsText.Value, 'Deserialized value mismatch');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestDataTypesJSON.TestTimestamp;
|
||||||
|
var
|
||||||
|
tsType: TDataType.TTimestamp;
|
||||||
|
tsValue: TDataType.TTimestamp.TValue;
|
||||||
|
deserializedValue: TDataType.TValue;
|
||||||
|
testVal: TDateTime;
|
||||||
|
begin
|
||||||
|
// Setup
|
||||||
|
testVal := EncodeDateTime(2025, 8, 27, 17, 30, 15, 500);
|
||||||
|
tsType := TDataType.Timestamp;
|
||||||
|
tsValue := tsType.CreateValue(testVal);
|
||||||
|
|
||||||
|
// Serialize
|
||||||
|
FJson := TDataJsonConverter.ValueToJson(tsValue);
|
||||||
|
Assert.IsTrue(FJson is TJSONString, 'Timestamp should serialize to JSON String');
|
||||||
|
Assert.AreEqual(DateToISO8601(testVal, true), (FJson as TJSONString).Value, 'Serialized value mismatch');
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
deserializedValue := TDataJsonConverter.JsonToValue(FJson, tsType);
|
||||||
|
Assert.AreEqual(dkTimestamp, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkTimestamp');
|
||||||
|
Assert.IsTrue(CompareDateTime(testVal, deserializedValue.AsTimestamp.Value) = 0, 'Deserialized value mismatch');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestDataTypesJSON.TestDecimal(const AValue: Int64; AScale: Integer; const AExpectedJsonString: string);
|
||||||
|
var
|
||||||
|
decType: TDataType.TDecimal;
|
||||||
|
decValue: TDataType.TDecimal.TValue;
|
||||||
|
deserializedValue: TDataType.TValue;
|
||||||
|
begin
|
||||||
|
// Setup
|
||||||
|
decType := TDataType.DecimalOf(AScale);
|
||||||
|
decValue := decType.CreateValue(AValue);
|
||||||
|
|
||||||
|
// Serialize
|
||||||
|
FJson := TDataJsonConverter.ValueToJson(decValue);
|
||||||
|
Assert.IsTrue(FJson is TJSONString, 'Decimal should serialize to JSON String');
|
||||||
|
Assert.AreEqual(AExpectedJsonString, (FJson as TJSONString).Value, 'Serialized string value mismatch');
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
deserializedValue := TDataJsonConverter.JsonToValue(FJson, decType);
|
||||||
|
Assert.AreEqual(dkDecimal, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkDecimal');
|
||||||
|
Assert.AreEqual(AValue, deserializedValue.AsDecimal.Value, 'Deserialized value mismatch');
|
||||||
|
Assert.AreEqual(AScale, deserializedValue.AsDecimal.DataType.Scale, 'Deserialized scale mismatch');
|
||||||
|
|
||||||
|
// Test Deserialization from Number (compatibility)
|
||||||
|
FJson.Free; // Free previous value before creating new one
|
||||||
|
FJson := TJSONNumber.Create(AValue / Power(10, AScale));
|
||||||
|
deserializedValue := TDataJsonConverter.JsonToValue(FJson, decType);
|
||||||
|
Assert.AreEqual(AValue, deserializedValue.AsDecimal.Value, 'Deserialized value from number mismatch');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestDataTypesJSON.TestEnum;
|
||||||
|
var
|
||||||
|
enumType: TDataType.TEnum;
|
||||||
|
enumValue: TDataType.TEnum.TValue;
|
||||||
|
deserializedValue: TDataType.TValue;
|
||||||
|
begin
|
||||||
|
// Setup
|
||||||
|
enumType := TDataType.EnumOf('MyEnum', ['Red', 'Green', 'Blue']);
|
||||||
|
enumValue := enumType.CreateValue(1); // Green
|
||||||
|
|
||||||
|
// Serialize
|
||||||
|
FJson := TDataJsonConverter.ValueToJson(enumValue);
|
||||||
|
Assert.IsTrue(FJson is TJSONString, 'Enum should serialize to JSON String');
|
||||||
|
Assert.AreEqual('Green', (FJson as TJSONString).Value, 'Serialized value mismatch');
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
deserializedValue := TDataJsonConverter.JsonToValue(FJson, enumType);
|
||||||
|
Assert.AreEqual(dkEnum, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkEnum');
|
||||||
|
Assert.AreEqual(1, deserializedValue.AsEnum.Value, 'Deserialized value mismatch');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestDataTypesJSON.TestRecord;
|
||||||
|
var
|
||||||
|
recordType: TDataType.TRecord;
|
||||||
|
idVal: TDataType.TOrdinal.TValue;
|
||||||
|
nameVal: TDataType.TText.TValue;
|
||||||
|
recordValue: TDataType.TRecord.TValue;
|
||||||
|
deserializedValue: TDataType.TValue;
|
||||||
|
begin
|
||||||
|
// Setup
|
||||||
|
recordType := TDataType.RecordOf([TDataRecordField.Create('ID', TDataType.Ordinal), TDataRecordField.Create('Name', TDataType.Text)]);
|
||||||
|
idVal := TDataType.Ordinal.CreateValue(99);
|
||||||
|
nameVal := TDataType.Text.CreateValue('TestRecord');
|
||||||
|
recordValue := recordType.CreateValue([idVal, nameVal]);
|
||||||
|
|
||||||
|
// Serialize
|
||||||
|
FJson := TDataJsonConverter.ValueToJson(recordValue);
|
||||||
|
Assert.IsTrue(FJson is TJSONObject, 'Record should serialize to JSON Object');
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
deserializedValue := TDataJsonConverter.JsonToValue(FJson, recordType);
|
||||||
|
Assert.AreEqual(dkRecord, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkRecord');
|
||||||
|
var desRecord := deserializedValue.AsRecord;
|
||||||
|
Assert.AreEqual(Int64(99), desRecord.Items[0].AsOrdinal.Value, 'Deserialized record field ID mismatch');
|
||||||
|
Assert.AreEqual('TestRecord', desRecord.Items[1].AsText.Value, 'Deserialized record field Name mismatch');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestDataTypesJSON.TestTuple;
|
||||||
|
var
|
||||||
|
tupleType: TDataType.TTuple;
|
||||||
|
idVal: TDataType.TOrdinal.TValue;
|
||||||
|
nameVal: TDataType.TText.TValue;
|
||||||
|
tupleValue: TDataType.TTuple.TValue;
|
||||||
|
deserializedValue: TDataType.TValue;
|
||||||
|
begin
|
||||||
|
// Setup
|
||||||
|
tupleType := TDataType.TupleOf([TDataType.Ordinal, TDataType.Text]);
|
||||||
|
idVal := TDataType.Ordinal.CreateValue(101);
|
||||||
|
nameVal := TDataType.Text.CreateValue('TestTuple');
|
||||||
|
tupleValue := tupleType.CreateValue([idVal, nameVal]);
|
||||||
|
|
||||||
|
// Serialize
|
||||||
|
FJson := TDataJsonConverter.ValueToJson(tupleValue);
|
||||||
|
Assert.IsTrue(FJson is TJSONArray, 'Tuple should serialize to JSON Array');
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
deserializedValue := TDataJsonConverter.JsonToValue(FJson, tupleType);
|
||||||
|
Assert.AreEqual(dkTuple, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkTuple');
|
||||||
|
var desTuple := deserializedValue.AsTuple;
|
||||||
|
Assert.AreEqual(Int64(101), desTuple.Items[0].AsOrdinal.Value, 'Deserialized tuple item 0 mismatch');
|
||||||
|
Assert.AreEqual('TestTuple', desTuple.Items[1].AsText.Value, 'Deserialized tuple item 1 mismatch');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestDataTypesJSON.TestArray;
|
||||||
|
var
|
||||||
|
arrayType: TDataType.TArray;
|
||||||
|
val1, val2: TDataType.TText.TValue;
|
||||||
|
arrayValue: TDataType.TArray.TValue;
|
||||||
|
deserializedValue: TDataType.TValue;
|
||||||
|
begin
|
||||||
|
// Setup
|
||||||
|
arrayType := TDataType.ArrayOf(TDataType.Text);
|
||||||
|
val1 := TDataType.Text.CreateValue('A');
|
||||||
|
val2 := TDataType.Text.CreateValue('B');
|
||||||
|
arrayValue := arrayType.CreateValue([val1, val2]);
|
||||||
|
|
||||||
|
// Serialize
|
||||||
|
FJson := TDataJsonConverter.ValueToJson(arrayValue);
|
||||||
|
Assert.IsTrue(FJson is TJSONArray, 'Array should serialize to JSON Array');
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
deserializedValue := TDataJsonConverter.JsonToValue(FJson, arrayType);
|
||||||
|
Assert.AreEqual(dkArray, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkArray');
|
||||||
|
var desArray := deserializedValue.AsArray;
|
||||||
|
Assert.AreEqual(2, desArray.ElementCount, 'Deserialized array element count mismatch');
|
||||||
|
Assert.AreEqual('A', desArray.Items[0].AsText.Value, 'Deserialized array item 0 mismatch');
|
||||||
|
Assert.AreEqual('B', desArray.Items[1].AsText.Value, 'Deserialized array item 1 mismatch');
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestDataTypesJSON.TestVector;
|
||||||
|
var
|
||||||
|
vectorType: TDataType.TVector;
|
||||||
|
val1, val2: TDataType.TOrdinal.TValue;
|
||||||
|
vectorValue: TDataType.TVector.TValue;
|
||||||
|
deserializedValue: TDataType.TValue;
|
||||||
|
jsonArray: TJSONArray;
|
||||||
|
begin
|
||||||
|
// Setup
|
||||||
|
vectorType := TDataType.VectorOf(TDataType.Ordinal, 2);
|
||||||
|
val1 := TDataType.Ordinal.CreateValue(10);
|
||||||
|
val2 := TDataType.Ordinal.CreateValue(20);
|
||||||
|
vectorValue := vectorType.CreateValue([val1, val2]);
|
||||||
|
|
||||||
|
// Serialize
|
||||||
|
FJson := TDataJsonConverter.ValueToJson(vectorValue);
|
||||||
|
Assert.IsTrue(FJson is TJSONArray, 'Vector should serialize to JSON Array');
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
deserializedValue := TDataJsonConverter.JsonToValue(FJson, vectorType);
|
||||||
|
Assert.AreEqual(dkVector, deserializedValue.DataType.Kind, 'Deserialized type kind should be dkVector');
|
||||||
|
var desVector := deserializedValue.AsVector;
|
||||||
|
Assert.AreEqual(2, desVector.ElementCount, 'Deserialized vector element count mismatch');
|
||||||
|
Assert.AreEqual(Int64(10), desVector.Items[0].AsOrdinal.Value, 'Deserialized vector item 0 mismatch');
|
||||||
|
Assert.AreEqual(Int64(20), desVector.Items[1].AsOrdinal.Value, 'Deserialized vector item 1 mismatch');
|
||||||
|
|
||||||
|
// Test wrong size deserialization
|
||||||
|
jsonArray := nil;
|
||||||
|
try
|
||||||
|
jsonArray := TJSONArray.Create;
|
||||||
|
jsonArray.Add(1);
|
||||||
|
Assert.WillRaise(
|
||||||
|
procedure begin TDataJsonConverter.JsonToValue(jsonArray, vectorType); end,
|
||||||
|
EConvertError,
|
||||||
|
'Deserializing a JSON array with wrong size for a vector should raise an exception'
|
||||||
|
);
|
||||||
|
finally
|
||||||
|
jsonArray.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestDataTypesJSON.TestMethodFails;
|
||||||
|
var
|
||||||
|
methodType: TDataType.TMethod;
|
||||||
|
methodValue: TDataType.TMethod.TValue;
|
||||||
|
begin
|
||||||
|
// Setup
|
||||||
|
methodType := TDataType.MethodOf(TDataType.Ordinal, TDataType.Ordinal);
|
||||||
|
methodValue := methodType.CreateValue(function(const AValue: TDataType.TValue): TDataType.TValue begin Result := AValue; end);
|
||||||
|
|
||||||
|
// Test Serialization
|
||||||
|
Assert.WillRaise(
|
||||||
|
procedure begin TDataJsonConverter.ValueToJson(methodValue).Free; end,
|
||||||
|
ENotSupportedException,
|
||||||
|
'Serializing a method should raise an exception'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test Deserialization
|
||||||
|
FJson := TJSONNull.Create;
|
||||||
|
//TODO Fails with "Method did not throw any exceptions. Deserializing a method should raise an exception"
|
||||||
|
Assert.WillRaise(
|
||||||
|
procedure begin TDataJsonConverter.JsonToValue(FJson, methodType); end,
|
||||||
|
ENotSupportedException,
|
||||||
|
'Deserializing a method should raise an exception'
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTestDataTypesJSON.TestNullValue;
|
||||||
|
var
|
||||||
|
value: TDataType.TValue;
|
||||||
|
deserializedValue: TDataType.TValue;
|
||||||
|
begin
|
||||||
|
// Setup
|
||||||
|
value := TDataType.Void.Value;
|
||||||
|
|
||||||
|
// Serialize
|
||||||
|
FJson := TDataJsonConverter.ValueToJson(value);
|
||||||
|
Assert.IsTrue(FJson is TJSONNull, 'nil IDataValue should serialize to JSON Null');
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
deserializedValue := TDataJsonConverter.JsonToValue(FJson, TDataType.Void);
|
||||||
|
Assert.IsTrue(deserializedValue.DataType.Kind = dkVoid, 'JSON Null should deserialize to a TValue with a nil DataValue');
|
||||||
|
end;
|
||||||
|
|
||||||
|
initialization
|
||||||
|
TDUnitX.RegisterTestFixture(TTestDataTypesJSON);
|
||||||
|
|
||||||
|
end.
|
||||||
+23
-22
@@ -8,7 +8,7 @@ uses
|
|||||||
type
|
type
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
[IgnoreMemoryLeaks]
|
[IgnoreMemoryLeaks]
|
||||||
TMyTestObject = class
|
TTestDataTypes = class
|
||||||
public
|
public
|
||||||
[Test]
|
[Test]
|
||||||
procedure TestRecords;
|
procedure TestRecords;
|
||||||
@@ -48,9 +48,10 @@ uses
|
|||||||
System.Rtti,
|
System.Rtti,
|
||||||
System.TypInfo,
|
System.TypInfo,
|
||||||
Myc.Data.Types,
|
Myc.Data.Types,
|
||||||
Myc.Data.Types.RTTI;
|
Myc.Data.Types.RTTI,
|
||||||
|
Myc.Data.Types.JSON;
|
||||||
|
|
||||||
procedure TMyTestObject.TestRecords;
|
procedure TTestDataTypes.TestRecords;
|
||||||
var
|
var
|
||||||
intType: TDataType.TOrdinal;
|
intType: TDataType.TOrdinal;
|
||||||
floatType: TDataType.TFloat;
|
floatType: TDataType.TFloat;
|
||||||
@@ -129,7 +130,7 @@ begin
|
|||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMyTestObject.TestRecords_Generic;
|
procedure TTestDataTypes.TestRecords_Generic;
|
||||||
var
|
var
|
||||||
dataType: TDataType.TRecord;
|
dataType: TDataType.TRecord;
|
||||||
dataValue: TDataType.TRecord.TValue;
|
dataValue: TDataType.TRecord.TValue;
|
||||||
@@ -159,7 +160,7 @@ begin
|
|||||||
Assert.AreEqual('<A: 1, B: two, C: 3>', IDataValue(dataValue).AsString, 'Generic record AsString is incorrect');
|
Assert.AreEqual('<A: 1, B: two, C: 3>', IDataValue(dataValue).AsString, 'Generic record AsString is incorrect');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMyTestObject.TestTuples;
|
procedure TTestDataTypes.TestTuples;
|
||||||
var
|
var
|
||||||
intValue: TDataType.TOrdinal.TValue;
|
intValue: TDataType.TOrdinal.TValue;
|
||||||
floatValue: TDataType.TFloat.TValue;
|
floatValue: TDataType.TFloat.TValue;
|
||||||
@@ -193,7 +194,7 @@ begin
|
|||||||
Assert.AreEqual('Tuple<Integer, Float>', type1.Name, 'The type name for all tuples should be Tuple');
|
Assert.AreEqual('Tuple<Integer, Float>', type1.Name, 'The type name for all tuples should be Tuple');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMyTestObject.TestTuples_Generic;
|
procedure TTestDataTypes.TestTuples_Generic;
|
||||||
var
|
var
|
||||||
tupleValue: TDataType.TTuple.TValue;
|
tupleValue: TDataType.TTuple.TValue;
|
||||||
item: TDataType.TOrdinal.TValue;
|
item: TDataType.TOrdinal.TValue;
|
||||||
@@ -219,7 +220,7 @@ begin
|
|||||||
Assert.AreEqual('(1, 2, 3, 4, 5, 6)', IDataValue(tupleValue).AsString, 'Generic tuple AsString is incorrect');
|
Assert.AreEqual('(1, 2, 3, 4, 5, 6)', IDataValue(tupleValue).AsString, 'Generic tuple AsString is incorrect');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMyTestObject.TestArrays;
|
procedure TTestDataTypes.TestArrays;
|
||||||
var
|
var
|
||||||
intType: TDataType.TOrdinal;
|
intType: TDataType.TOrdinal;
|
||||||
floatType: TDataType.TFloat;
|
floatType: TDataType.TFloat;
|
||||||
@@ -283,7 +284,7 @@ begin
|
|||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMyTestObject.TestArrays_OptimizedAndGeneric;
|
procedure TTestDataTypes.TestArrays_OptimizedAndGeneric;
|
||||||
var
|
var
|
||||||
arrayType: TDataType.TArray;
|
arrayType: TDataType.TArray;
|
||||||
empty1, empty2, single, generic: TDataType.TArray.TValue;
|
empty1, empty2, single, generic: TDataType.TArray.TValue;
|
||||||
@@ -316,7 +317,7 @@ begin
|
|||||||
Assert.AreEqual('[1, 2, 3]', IDataValue(generic).AsString, 'Generic array AsString is incorrect');
|
Assert.AreEqual('[1, 2, 3]', IDataValue(generic).AsString, 'Generic array AsString is incorrect');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMyTestObject.TestTexts;
|
procedure TTestDataTypes.TestTexts;
|
||||||
var
|
var
|
||||||
textType: TDataType.TText;
|
textType: TDataType.TText;
|
||||||
textValue1, textValue2, castedValue: TDataType.TText.TValue;
|
textValue1, textValue2, castedValue: TDataType.TText.TValue;
|
||||||
@@ -354,7 +355,7 @@ begin
|
|||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMyTestObject.TestTexts_OptimizedEmpty;
|
procedure TTestDataTypes.TestTexts_OptimizedEmpty;
|
||||||
var
|
var
|
||||||
empty1, empty2: TDataType.TText.TValue;
|
empty1, empty2: TDataType.TText.TValue;
|
||||||
begin
|
begin
|
||||||
@@ -368,7 +369,7 @@ begin
|
|||||||
Assert.AreEqual('', IDataValue(empty1).AsString, 'AsString of empty text should be empty');
|
Assert.AreEqual('', IDataValue(empty1).AsString, 'AsString of empty text should be empty');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMyTestObject.TestFloats_OptimizedAndGeneric;
|
procedure TTestDataTypes.TestFloats_OptimizedAndGeneric;
|
||||||
var
|
var
|
||||||
zero1, zero2, nan1, nan2, generic: TDataType.TFloat.TValue;
|
zero1, zero2, nan1, nan2, generic: TDataType.TFloat.TValue;
|
||||||
begin
|
begin
|
||||||
@@ -395,7 +396,7 @@ begin
|
|||||||
Assert.AreEqual(123.45, generic.Value, 'Value of generic float is incorrect');
|
Assert.AreEqual(123.45, generic.Value, 'Value of generic float is incorrect');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMyTestObject.TestTimestamps;
|
procedure TTestDataTypes.TestTimestamps;
|
||||||
var
|
var
|
||||||
tsType: TDataType.TTimestamp;
|
tsType: TDataType.TTimestamp;
|
||||||
tsValue1, tsValue2, castedValue: TDataType.TTimestamp.TValue;
|
tsValue1, tsValue2, castedValue: TDataType.TTimestamp.TValue;
|
||||||
@@ -435,7 +436,7 @@ begin
|
|||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMyTestObject.TestEnums;
|
procedure TTestDataTypes.TestEnums;
|
||||||
var
|
var
|
||||||
colorType: TDataType.TEnum;
|
colorType: TDataType.TEnum;
|
||||||
red, green, blue: TDataType.TEnum.TValue;
|
red, green, blue: TDataType.TEnum.TValue;
|
||||||
@@ -490,7 +491,7 @@ begin
|
|||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMyTestObject.TestAsString;
|
procedure TTestDataTypes.TestAsString;
|
||||||
var
|
var
|
||||||
ordinalValue: TDataType.TOrdinal.TValue;
|
ordinalValue: TDataType.TOrdinal.TValue;
|
||||||
floatValue: TDataType.TFloat.TValue;
|
floatValue: TDataType.TFloat.TValue;
|
||||||
@@ -542,7 +543,7 @@ begin
|
|||||||
Assert.AreEqual('(10, Tuple)', IDataValue(tupleValue).AsString, 'Tuple AsString incorrect');
|
Assert.AreEqual('(10, Tuple)', IDataValue(tupleValue).AsString, 'Tuple AsString incorrect');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMyTestObject.TestAsTValue;
|
procedure TTestDataTypes.TestAsTValue;
|
||||||
var
|
var
|
||||||
dataValue: TDataType.TValue; // Keep as generic interface to test AsTValue
|
dataValue: TDataType.TValue; // Keep as generic interface to test AsTValue
|
||||||
now: TDateTime;
|
now: TDateTime;
|
||||||
@@ -551,21 +552,21 @@ begin
|
|||||||
// This test is specifically for the IDataValue.AsTValue method. No changes needed.
|
// This test is specifically for the IDataValue.AsTValue method. No changes needed.
|
||||||
// --- Ordinal ---
|
// --- Ordinal ---
|
||||||
dataValue := TDataType.Ordinal.CreateValue(123);
|
dataValue := TDataType.Ordinal.CreateValue(123);
|
||||||
var tv := dataValue.AsTValue;
|
var tv := AsTValue(dataValue);
|
||||||
Assert.IsFalse(tv.IsEmpty, 'Ordinal TValue should not be empty');
|
Assert.IsFalse(tv.IsEmpty, 'Ordinal TValue should not be empty');
|
||||||
Assert.AreEqual(tkInt64, tv.Kind, 'Ordinal TValue kind should be tkInt64');
|
Assert.AreEqual(tkInt64, tv.Kind, 'Ordinal TValue kind should be tkInt64');
|
||||||
Assert.AreEqual(Int64(123), tv.AsInt64, 'Ordinal TValue content is incorrect');
|
Assert.AreEqual(Int64(123), tv.AsInt64, 'Ordinal TValue content is incorrect');
|
||||||
|
|
||||||
// --- Float ---
|
// --- Float ---
|
||||||
dataValue := TDataType.Float.CreateValue(45.67);
|
dataValue := TDataType.Float.CreateValue(45.67);
|
||||||
tv := dataValue.AsTValue;
|
tv := AsTValue(dataValue);
|
||||||
Assert.IsFalse(tv.IsEmpty, 'Float TValue should not be empty');
|
Assert.IsFalse(tv.IsEmpty, 'Float TValue should not be empty');
|
||||||
Assert.AreEqual(tkFloat, tv.Kind, 'Float TValue kind should be tkFloat');
|
Assert.AreEqual(tkFloat, tv.Kind, 'Float TValue kind should be tkFloat');
|
||||||
Assert.AreEqual(45.67, tv.AsExtended, 'Float TValue content is incorrect');
|
Assert.AreEqual(45.67, tv.AsExtended, 'Float TValue content is incorrect');
|
||||||
|
|
||||||
// --- Text ---
|
// --- Text ---
|
||||||
dataValue := TDataType.Text.CreateValue('Hello');
|
dataValue := TDataType.Text.CreateValue('Hello');
|
||||||
tv := dataValue.AsTValue;
|
tv := AsTValue(dataValue);
|
||||||
Assert.IsFalse(tv.IsEmpty, 'Text TValue should not be empty');
|
Assert.IsFalse(tv.IsEmpty, 'Text TValue should not be empty');
|
||||||
Assert.AreEqual(tkUString, tv.Kind, 'Text TValue kind should be tkUString');
|
Assert.AreEqual(tkUString, tv.Kind, 'Text TValue kind should be tkUString');
|
||||||
Assert.AreEqual('Hello', tv.AsString, 'Text TValue content is incorrect');
|
Assert.AreEqual('Hello', tv.AsString, 'Text TValue content is incorrect');
|
||||||
@@ -573,7 +574,7 @@ begin
|
|||||||
// --- Timestamp ---
|
// --- Timestamp ---
|
||||||
now := System.SysUtils.Now;
|
now := System.SysUtils.Now;
|
||||||
dataValue := TDataType.Timestamp.CreateValue(now);
|
dataValue := TDataType.Timestamp.CreateValue(now);
|
||||||
tv := dataValue.AsTValue;
|
tv := AsTValue(dataValue);
|
||||||
Assert.IsFalse(tv.IsEmpty, 'Timestamp TValue should not be empty');
|
Assert.IsFalse(tv.IsEmpty, 'Timestamp TValue should not be empty');
|
||||||
Assert.AreEqual(tkFloat, tv.Kind, 'Timestamp TValue kind is incorrect');
|
Assert.AreEqual(tkFloat, tv.Kind, 'Timestamp TValue kind is incorrect');
|
||||||
Assert.AreEqual(now, tv.AsType<TDateTime>, 'Timestamp TValue content is incorrect');
|
Assert.AreEqual(now, tv.AsType<TDateTime>, 'Timestamp TValue content is incorrect');
|
||||||
@@ -581,13 +582,13 @@ begin
|
|||||||
// --- Enum ---
|
// --- Enum ---
|
||||||
colorType := TDataType.EnumOf('Color', ['Red', 'Green', 'Blue']);
|
colorType := TDataType.EnumOf('Color', ['Red', 'Green', 'Blue']);
|
||||||
dataValue := colorType.CreateValue('Green');
|
dataValue := colorType.CreateValue('Green');
|
||||||
tv := dataValue.AsTValue;
|
tv := AsTValue(dataValue);
|
||||||
Assert.IsFalse(tv.IsEmpty, 'Enum TValue should not be empty');
|
Assert.IsFalse(tv.IsEmpty, 'Enum TValue should not be empty');
|
||||||
Assert.AreEqual(tkInteger, tv.Kind, 'Enum TValue kind should be tkInteger');
|
Assert.AreEqual(tkInteger, tv.Kind, 'Enum TValue kind should be tkInteger');
|
||||||
Assert.AreEqual(1, tv.AsInteger, 'Enum TValue content is incorrect');
|
Assert.AreEqual(1, tv.AsInteger, 'Enum TValue content is incorrect');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMyTestObject.TestMethods;
|
procedure TTestDataTypes.TestMethods;
|
||||||
var
|
var
|
||||||
ordinalType: TDataType.TOrdinal;
|
ordinalType: TDataType.TOrdinal;
|
||||||
textType: TDataType.TText;
|
textType: TDataType.TText;
|
||||||
@@ -658,7 +659,7 @@ begin
|
|||||||
// --- 5. Test AsString and AsTValue ---
|
// --- 5. Test AsString and AsTValue ---
|
||||||
Assert.AreEqual('<METHOD>', IDataValue(methodValue).AsString, 'Method AsString is incorrect');
|
Assert.AreEqual('<METHOD>', IDataValue(methodValue).AsString, 'Method AsString is incorrect');
|
||||||
|
|
||||||
tv := TDataType.TValue(methodValue).AsTValue;
|
tv := AsTValue(methodValue);
|
||||||
Assert.IsFalse(tv.IsEmpty, 'Method TValue should not be empty');
|
Assert.IsFalse(tv.IsEmpty, 'Method TValue should not be empty');
|
||||||
Assert.AreEqual(tkInterface, tv.Kind, 'TValue kind for a TMethodProc should be tkInterface, as it is a reference-to-function');
|
Assert.AreEqual(tkInterface, tv.Kind, 'TValue kind for a TMethodProc should be tkInterface, as it is a reference-to-function');
|
||||||
Assert.IsTrue(TypeInfo(TDataMethodProc) = tv.TypeInfo, 'TValue should hold TMethodProc type info');
|
Assert.IsTrue(TypeInfo(TDataMethodProc) = tv.TypeInfo, 'TValue should hold TMethodProc type info');
|
||||||
|
|||||||
+3
-1
@@ -35,7 +35,9 @@ uses
|
|||||||
Myc.Data.Types.Void in '..\Src\Data\Myc.Data.Types.Void.pas',
|
Myc.Data.Types.Void in '..\Src\Data\Myc.Data.Types.Void.pas',
|
||||||
Myc.Data.Types.Decimal in '..\Src\Data\Myc.Data.Types.Decimal.pas',
|
Myc.Data.Types.Decimal in '..\Src\Data\Myc.Data.Types.Decimal.pas',
|
||||||
Myc.Ast in '..\Src\AST\Myc.Ast.pas',
|
Myc.Ast in '..\Src\AST\Myc.Ast.pas',
|
||||||
TestAst in '..\Src\AST\TestAst.pas';
|
TestAst in '..\Src\AST\TestAst.pas',
|
||||||
|
Myc.Data.Types.JSON in '..\Src\Data\Myc.Data.Types.JSON.pas',
|
||||||
|
TestDataTypes.JSON in '..\Src\Data\TestDataTypes.JSON.pas';
|
||||||
|
|
||||||
{ keep comment here to protect the following conditional from being removed by the IDE when adding a unit }
|
{ keep comment here to protect the following conditional from being removed by the IDE when adding a unit }
|
||||||
{$IFNDEF TESTINSIGHT}
|
{$IFNDEF TESTINSIGHT}
|
||||||
|
|||||||
@@ -137,6 +137,8 @@ $(PreBuildEvent)]]></PreBuildEvent>
|
|||||||
<DCCReference Include="..\Src\Data\Myc.Data.Types.Decimal.pas"/>
|
<DCCReference Include="..\Src\Data\Myc.Data.Types.Decimal.pas"/>
|
||||||
<DCCReference Include="..\Src\AST\Myc.Ast.pas"/>
|
<DCCReference Include="..\Src\AST\Myc.Ast.pas"/>
|
||||||
<DCCReference Include="..\Src\AST\TestAst.pas"/>
|
<DCCReference Include="..\Src\AST\TestAst.pas"/>
|
||||||
|
<DCCReference Include="..\Src\Data\Myc.Data.Types.JSON.pas"/>
|
||||||
|
<DCCReference Include="..\Src\Data\TestDataTypes.JSON.pas"/>
|
||||||
<BuildConfiguration Include="Base">
|
<BuildConfiguration Include="Base">
|
||||||
<Key>Base</Key>
|
<Key>Base</Key>
|
||||||
</BuildConfiguration>
|
</BuildConfiguration>
|
||||||
|
|||||||
Reference in New Issue
Block a user