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.
|
||||
Reference in New Issue
Block a user