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 private // Serialization helpers for specific data types class function OrdinalToJson(const Value: TDataType.TOrdinal.TValue): TJSONValue; static; class function FloatToJson(const Value: TDataType.TFloat.TValue): TJSONValue; static; class function TextToJson(const Value: TDataType.TText.TValue): TJSONValue; static; class function TimestampToJson(const Value: TDataType.TTimestamp.TValue): TJSONValue; static; class function DecimalToJson(const Value: TDataType.TDecimal.TValue): TJSONValue; static; class function EnumToJson(const Value: TDataType.TEnum.TValue): TJSONValue; static; class function RecordToJson(const Value: TDataType.TRecord.TValue): TJSONObject; static; class function TupleToJson(const Value: TDataType.TTuple.TValue): TJSONArray; static; class function ArrayToJson(const Value: TDataType.TArray.TValue): TJSONArray; static; class function VectorToJson(const Value: TDataType.TVector.TValue): TJSONArray; static; // Deserialization helpers for specific data types class function JsonToOrdinal(const Json: TJSONValue; const DataType: TDataType): TDataType.TOrdinal.TValue; static; class function JsonToFloat(const Json: TJSONValue; const DataType: TDataType): TDataType.TFloat.TValue; static; class function JsonToText(const Json: TJSONValue; const DataType: TDataType): TDataType.TText.TValue; static; class function JsonToTimestamp(const Json: TJSONValue; const DataType: TDataType): TDataType.TTimestamp.TValue; static; class function JsonToDecimal(const Json: TJSONValue; const DataType: TDataType): TDataType.TDecimal.TValue; static; class function JsonToEnum(const Json: TJSONValue; const DataType: TDataType): TDataType.TEnum.TValue; static; class function JsonToRecord(const Json: TJSONObject; const DataType: TDataType): TDataType.TRecord.TValue; static; class function JsonToTuple(const Json: TJSONArray; const DataType: TDataType): TDataType.TTuple.TValue; static; class function JsonToArray(const Json: TJSONArray; const DataType: TDataType): TDataType.TArray.TValue; static; class function JsonToVector(const Json: TJSONArray; const DataType: TDataType): TDataType.TVector.TValue; static; 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; class function TDataJsonConverter.OrdinalToJson(const Value: TDataType.TOrdinal.TValue): TJSONValue; begin Result := TJSONNumber.Create(Value.Value); end; class function TDataJsonConverter.FloatToJson(const Value: TDataType.TFloat.TValue): TJSONValue; begin Result := TJSONNumber.Create(Value.Value); end; class function TDataJsonConverter.TextToJson(const Value: TDataType.TText.TValue): TJSONValue; begin Result := TJSONString.Create(Value.Value); end; class function TDataJsonConverter.TimestampToJson(const Value: TDataType.TTimestamp.TValue): TJSONValue; begin Result := TJSONString.Create(DateToISO8601(Value.Value, true)); end; class function TDataJsonConverter.DecimalToJson(const Value: TDataType.TDecimal.TValue): TJSONValue; var val: Int64; scale: Integer; s: string; begin // Serialize as string to preserve precision. val := Value.Value; scale := Value.DataType.Scale; 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; class function TDataJsonConverter.EnumToJson(const Value: TDataType.TEnum.TValue): TJSONValue; begin Result := TJSONString.Create(Value.DataType.Identifiers[Value.Value]); end; class function TDataJsonConverter.RecordToJson(const Value: TDataType.TRecord.TValue): TJSONObject; var i: Integer; recordType: TDataType.TRecord; begin recordType := Value.DataType; Result := TJSONObject.Create; for i := 0 to recordType.FieldCount - 1 do Result.AddPair(recordType.Fields[i].Name, ValueToJson(Value.Items[i])); end; class function TDataJsonConverter.TupleToJson(const Value: TDataType.TTuple.TValue): TJSONArray; var i: Integer; begin Result := TJSONArray.Create; for i := 0 to Value.ItemCount - 1 do Result.AddElement(ValueToJson(Value.Items[i])); end; class function TDataJsonConverter.ArrayToJson(const Value: TDataType.TArray.TValue): TJSONArray; var i: Integer; begin Result := TJSONArray.Create; for i := 0 to Value.ElementCount - 1 do Result.AddElement(ValueToJson(Value.Items[i])); end; class function TDataJsonConverter.VectorToJson(const Value: TDataType.TVector.TValue): TJSONArray; var i: Integer; begin Result := TJSONArray.Create; for i := 0 to Value.ElementCount - 1 do Result.AddElement(ValueToJson(Value.Items[i])); end; { TDataJsonConverter - Deserialization Helpers } class function TDataJsonConverter.JsonToOrdinal(const Json: TJSONValue; const DataType: TDataType): TDataType.TOrdinal.TValue; var ordinalType: TDataType.TOrdinal; begin Assert(DataType.Kind = dkOrdinal); ordinalType := DataType.AsOrdinal; Result := ordinalType.CreateValue((Json as TJSONNumber).AsInt64); end; class function TDataJsonConverter.JsonToFloat(const Json: TJSONValue; const DataType: TDataType): TDataType.TFloat.TValue; var floatType: TDataType.TFloat; begin Assert(DataType.Kind = dkFloat); floatType := DataType.AsFloat; Result := floatType.CreateValue((Json as TJSONNumber).AsDouble); end; class function TDataJsonConverter.JsonToText(const Json: TJSONValue; const DataType: TDataType): TDataType.TText.TValue; var textType: TDataType.TText; begin Assert(DataType.Kind = dkText); textType := DataType.AsText; Result := textType.CreateValue((Json as TJSONString).Value); end; class function TDataJsonConverter.JsonToTimestamp(const Json: TJSONValue; const DataType: TDataType): TDataType.TTimestamp.TValue; var timestampType: TDataType.TTimestamp; begin Assert(DataType.Kind = dkTimestamp); timestampType := DataType.AsTimestamp; Result := timestampType.CreateValue(ISO8601ToDate((Json as TJSONString).Value, True)); end; class function TDataJsonConverter.JsonToDecimal(const Json: TJSONValue; const DataType: TDataType): TDataType.TDecimal.TValue; var val: Int64; i: Integer; decimalType: TDataType.TDecimal; begin Assert(DataType.Kind = dkDecimal); decimalType := DataType.AsDecimal; // Handle string representation for precision, with fallback to number for compatibility. 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; class function TDataJsonConverter.JsonToEnum(const Json: TJSONValue; const DataType: TDataType): TDataType.TEnum.TValue; var idx: Integer; enumType: TDataType.TEnum; begin Assert(DataType.Kind = dkEnum); 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; class function TDataJsonConverter.JsonToRecord(const Json: TJSONObject; const DataType: TDataType): TDataType.TRecord.TValue; var items: TArray; i: Integer; recordType: TDataType.TRecord; begin Assert(DataType.Kind = dkRecord); recordType := DataType.AsRecord; SetLength(items, recordType.FieldCount); for i := 0 to recordType.FieldCount - 1 do begin var field := recordType.Fields[i]; var jsonPair := Json.Get(field.Name); if Assigned(jsonPair) then items[i] := JsonToValue(jsonPair.JsonValue, field.DataType); end; Result := recordType.CreateValue(items); end; class function TDataJsonConverter.JsonToTuple(const Json: TJSONArray; const DataType: TDataType): TDataType.TTuple.TValue; var items: TArray; i: Integer; tupleType: TDataType.TTuple; begin Assert(DataType.Kind = dkTuple); tupleType := DataType.AsTuple; if (Json.Count <> tupleType.ItemCount) then raise EConvertError.CreateFmt('JSON array size (%d) does not match tuple size (%d)', [Json.Count, tupleType.ItemCount]); SetLength(items, tupleType.ItemCount); for i := 0 to tupleType.ItemCount - 1 do items[i] := JsonToValue(Json.Items[i], tupleType.ItemType[i]); Result := tupleType.CreateValue(items); end; class function TDataJsonConverter.JsonToArray(const Json: TJSONArray; const DataType: TDataType): TDataType.TArray.TValue; var items: TArray; i: Integer; arrayType: TDataType.TArray; begin Assert(DataType.Kind = dkArray); arrayType := DataType.AsArray; SetLength(items, Json.Count); for i := 0 to Json.Count - 1 do items[i] := JsonToValue(Json.Items[i], arrayType.ElementType); Result := arrayType.CreateValue(items); end; class function TDataJsonConverter.JsonToVector(const Json: TJSONArray; const DataType: TDataType): TDataType.TVector.TValue; var items: TArray; i: Integer; vectorType: TDataType.TVector; begin Assert(DataType.Kind = dkVector); vectorType := DataType.AsVector; if (Json.Count <> vectorType.ElementCount) then raise EConvertError.CreateFmt('JSON array size (%d) does not match vector size (%d)', [Json.Count, vectorType.ElementCount]); SetLength(items, Json.Count); for i := 0 to Json.Count - 1 do items[i] := JsonToValue(Json.Items[i], vectorType.ElementType); Result := vectorType.CreateValue(items); end; { TDataJsonConverter - Public Methods } class function TDataJsonConverter.ValueToJson(const Value: TDataType.TValue): TJSONValue; begin case Value.DataType.Kind of dkVoid: Result := TJSONNull.Create; dkOrdinal: Result := OrdinalToJson(Value.AsOrdinal); dkFloat: Result := FloatToJson(Value.AsFloat); dkText: Result := TextToJson(Value.AsText); dkTimestamp: Result := TimestampToJson(Value.AsTimestamp); dkDecimal: Result := DecimalToJson(Value.AsDecimal); dkEnum: Result := EnumToJson(Value.AsEnum); dkRecord: Result := RecordToJson(Value.AsRecord); dkTuple: Result := TupleToJson(Value.AsTuple); dkArray: Result := ArrayToJson(Value.AsArray); dkVector: Result := VectorToJson(Value.AsVector); 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; begin case DataType.Kind of dkVoid: Result := TDataType.Void.Value; dkOrdinal: Result := JsonToOrdinal(Json, DataType); dkFloat: Result := JsonToFloat(Json, DataType); dkText: Result := JsonToText(Json, DataType); dkTimestamp: Result := JsonToTimestamp(Json, DataType); dkDecimal: Result := JsonToDecimal(Json, DataType); dkEnum: Result := JsonToEnum(Json, DataType); dkRecord: Result := JsonToRecord(Json as TJSONObject, DataType); dkTuple: Result := JsonToTuple(Json as TJSONArray, DataType); dkArray: Result := JsonToArray(Json as TJSONArray, DataType); dkVector: Result := JsonToVector(Json as TJSONArray, DataType); 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.