unit Bcl.Json.Converters;

{$I Bcl.Inc}

interface

uses
  Generics.Collections, SysUtils, Rtti, TypInfo,
  Bcl.Json.Reader,
  Bcl.Json.Writer,
  Bcl.Rtti.ObjectFactory;

type
  TTypeToken = record
  strict private
    FTypeInfo: PTypeInfo;
    FItemTypeInfo: PTypeInfo;
    function ClassFromTypeInfo(ATypeInfo: PTypeInfo): TClass;
  private
    class function FromTypeInfo(const ATypeInfo: PTypeInfo): TTypeToken; static;
    class function FromClass(const AClass: TClass): TTypeToken; static;
  public
    constructor Create(const ATypeInfo: PTypeInfo); overload;
    constructor Create(const ATypeInfo, AItemTypeInfo: PTypeInfo); overload;

    class operator Equal(const Left, Right: TTypeToken): Boolean;
    class operator Implicit(ASource: PTypeInfo): TTypeToken;
    class operator Implicit(ASource: TClass): TTypeToken;
    class operator Implicit(ASource: TTypeToken): PTypeInfo;

    function IsClass: Boolean;
    function GetClass: TClass;
    function GetItemClass: TClass;
    property TypeInfo: PTypeInfo read FTypeInfo;
    property ItemTypeInfo: PTypeInfo read FItemTypeInfo;
  end;

  TJsonConverters = class;

  IJsonTypeConverter = interface
  ['{6695C962-92AA-47E6-A305-7C2847713AC6}']
    procedure WriteJson(const Writer: TJsonWriter; const Value: TValue);
    procedure ReadJson(const Reader: TJsonReader; var Value: TValue);
  end;

  IJsonConverterFactory = interface
  ['{9A9A679C-8B4D-4CAA-88E7-0D4A38AC4B14}']
    function CreateConverter(Converters: TJsonConverters; const ATypeToken: TTypeToken): IJsonTypeConverter;
  end;

  IJsonNamingStrategy = interface
  ['{CB68B5E0-CEB7-4200-A5A8-94D387364DE7}']
    function ResolveName(RttiMember: TRttiMember): string;
  end;

  TJsonNamingStrategy = class(TInterfacedObject, IJsonNamingStrategy)
  protected
    function ResolveName(RttiMember: TRttiMember): string; virtual; abstract;
  end;

  TJsonNamingStrategyClass = class of TJsonNamingStrategy;

  TJsonTypeConverter = class abstract(TInterfacedObject, IJsonTypeConverter)
  strict private
    type
      EConvertToJsonError = class(Exception)
      public
        constructor Create(const TypeName: string; Value: TValue);
      end;
      EConvertFromJsonError = class(Exception)
      public
        constructor Create(const TypeName: string);
      end;
  protected
    function GetTypeToken: TTypeToken; virtual; abstract;
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; virtual;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; virtual;
  public
    procedure WriteJson(const Writer: TJsonWriter; const Value: TValue);
    procedure ReadJson(const Reader: TJsonReader; var Value: TValue);
  end;

  TJsonTypeConverter<T> = class abstract(TJsonTypeConverter)
  protected
    function GetTypeToken: TTypeToken; override;
  end;

  TJsonConverterStaticFactory = class(TInterfacedObject, IJsonConverterFactory)
  strict private
    FTypeToken: TTypeToken;
    FConverter: IJsonTypeConverter;
  strict protected
    function CreateConverter(Converters: TJsonConverters; const ATypeToken: TTypeToken): IJsonTypeConverter;
  public
    constructor Create(const ATypeToken: TTypeToken; AConverter: IJsonTypeConverter);
  end;

  TJsonConverterStaticFactory<C: TJsonTypeConverter, constructor> = class
  strict private
    class var
      FFactory: IJsonConverterFactory;
  strict private
    class function GetFactory: IJsonConverterFactory; static;
  public
    class destructor Destroy;
    class property Factory: IJsonConverterFactory read GetFactory;
  end;

  TInstanceRefSerialization = (Always, IfRecursive);
  TInstanceTypeSerialization = (Always, IfNeeded);
  TInstanceLoopHandling = (Error, Ignore, Reference);
  TUnknownMemberHandling = (Ignore, Error);
  TInclusionMode = (Always, NonDefault);

  TJsonAbstractObjectConverterFactory = class(TInterfacedObject, IJsonConverterFactory)
  strict private
    FInstanceRefSerialization: TInstanceRefSerialization;
    FInstanceTypeSerialization: TInstanceTypeSerialization;
    FInstanceLoopHandling: TInstanceLoopHandling;
    FUnknownMemberHandling: TUnknownMemberHandling;
    FInclusionMode: TInclusionMode;
  public
    function CreateConverter(Converters: TJsonConverters; const ATypeToken: TTypeToken): IJsonTypeConverter; virtual; abstract;
  public
    property InstanceRefSerialization: TInstanceRefSerialization read FInstanceRefSerialization write FInstanceRefSerialization;
    property InstanceTypeSerialization: TInstanceTypeSerialization read FInstanceTypeSerialization write FInstanceTypeSerialization;
    property InstanceLoopHandling: TInstanceLoopHandling read FInstanceLoopHandling write FInstanceLoopHandling;
    property UnknownMemberHandling: TUnknownMemberHandling read FUnknownMemberHandling write FUnknownMemberHandling;
    property InclusionMode: TInclusionMode read FInclusionMode write FInclusionMode;
  end;

  TConverterCreatedProc = reference to procedure(const ATypeToken: TTypeToken;
    Converter: IJsonTypeConverter);

  TJsonConverters = class(TInterfacedObject)
  public
    type
      ETypeConverterNotFound = class(Exception)
      public
        constructor Create(const ATypeToken: TTypeToken);
      end;
//  strict private
//    class var
//      FDefault: TJsonConverters;
  strict private
    FConverters: TDictionary<TTypeToken, IJsonTypeConverter>;
    FCustomFactories: TList<IJsonConverterFactory>;
    FObjectConverterFactory: TJsonAbstractObjectConverterFactory;
    FObjectConverterFactoryIntf: IJsonConverterFactory;
    // For now, OnObjectProcessed is used only internally and in all situations
    // the event handler causes a cycle. In future this should be reviewed for a more generic purpose
    // (or the callers must find a way to avoid the cycle)
    {$IFDEF AUTOREFCOUNT} [Weak] {$ENDIF}
    FOnObjectProcessed: TProc<TObject>;
    FObjectFactory: IObjectFactory;
    FOnConverterCreated: TConverterCreatedProc;
    procedure SetOnObjectProcessed(const Value: TProc<TObject>);
    function InternalCreateConverter(Factory: IJsonConverterFactory;
      const ATypeToken: TTypeToken; out Converter: IJsonTypeConverter): Boolean;
  strict protected
    function GetObjectConverterFactory: TJsonAbstractObjectConverterFactory; virtual;
    procedure ConverterCreated(const ATypeToken: TTypeToken; Converter: IJsonTypeConverter); virtual;
  public
//    class function Default: TJsonConverters; static;
//    class destructor Destroy;
    procedure ObjectCreated(Value: TObject);
    procedure ObjectWritten(Value: TObject);
  public
    constructor Create;
    destructor Destroy; override;
    procedure AddFactory(AFactory: IJsonConverterFactory);
    function Find(const ATypeToken: TTypeToken): IJsonTypeConverter;
    function Get(const ATypeToken: TTypeToken): IJsonTypeConverter;
    property OnObjectProcessed: TProc<TObject> write SetOnObjectProcessed;
    property OnConverterCreated: TConverterCreatedProc read FOnConverterCreated write FOnConverterCreated;
    property ObjectConverterFactory: TJsonAbstractObjectConverterFactory read GetObjectConverterFactory;
    property ObjectFactory: IObjectFactory read FObjectFactory write FObjectFactory;
  end;

  EJsonConverterNotFound = class(Exception)
  public
    constructor Create(const ATypeToken: TTypeToken);
  end;

{$REGION 'Primitive converters'}
type
  TJsonInt32Converter<T> = class abstract(TJsonTypeConverter<T>)
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  end;

  TJsonIntegerConverter = class(TJsonInt32Converter<Integer>);
  TJsonLongintConverter = class(TJsonInt32Converter<Longint>);
  TJsonWordConverter = class(TJsonInt32Converter<Word>);

  TJsonInt16Converter<T> = class abstract(TJsonTypeConverter<T>)
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  end;

  TJsonShortintConverter = class(TJsonInt16Converter<Shortint>);
  TJsonByteConverter = class(TJsonInt16Converter<Byte>);
  TJsonSmallIntConverter=  class(TJsonInt16Converter<SmallInt>);

  TJsonInt64Converter<T> = class abstract(TJsonTypeConverter<T>)
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  end;

  TJsonCardinalConverter = class(TJsonInt64Converter<Cardinal>);
  TJsonLongwordConverter = class(TJsonInt64Converter<Longword>);
  TJsonInt64Converter = class(TJsonInt64Converter<Int64>);

  TJsonBooleanConverter = class(TJsonTypeConverter<Boolean>)
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  end;

  TJsonDoubleConverter<T> = class abstract(TJsonTypeConverter<T>)
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  end;

  TJsonDoubleConverter = class(TJsonDoubleConverter<Double>);
  TJsonSingleConverter = class(TJsonDoubleConverter<Single>);
  TJsonExtendedConverter = class(TJsonDoubleConverter<Extended>);
  TJsonRealConverter = class(TJsonDoubleConverter<Real>)
  public
    function GetTypeToken: TTypeToken; override;
  end;

  TJsonCurrencyConverter = class abstract(TJsonTypeConverter<Currency>)
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  end;

  TJsonDateTimeConverter = class(TJsonTypeConverter<TDateTime>)
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  end;

  TJsonDateConverter = class(TJsonTypeConverter<TDate>)
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  end;

  TJsonTimeConverter = class(TJsonTypeConverter<TTime>)
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  end;

  TJsonBinaryConverter<T> = class abstract(TJsonTypeConverter<T>)
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  end;

  TJsonBytesConverter = class(TJsonBinaryConverter<TBytes>);
  TJsonArrayByteConverter = class(TJsonBinaryConverter<TArray<byte>>);

  TJsonStringConverter<T> = class abstract(TJsonTypeConverter<T>)
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  end;

  TJsonStringConverter = class(TJsonStringConverter<string>);
  {$IFNDEF NEXTGEN}
  TJsonAnsiStringConverter = class(TJsonStringConverter<AnsiString>);
  TJsonWideStringConverter = class(TJsonStringConverter<WideString>);
  {$ENDIF}

  TJsonCharConverter<T> = class abstract(TJsonTypeConverter<T>)
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  end;

  TJsonCharConverter = class(TJsonCharConverter<Char>);
  {$IFNDEF NEXTGEN}
  TJsonAnsiCharConverter = class(TJsonCharConverter<AnsiChar>);
  TJsonWideCharConverter = class(TJsonCharConverter<WideChar>);
  {$ENDIF}

  TJsonGuidConverter = class(TJsonTypeConverter<TGuid>)
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  end;

  TJsonVariantConverter = class(TJsonTypeConverter<Variant>)
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  end;

//  TJsonRttiValueConverter = class(TJsonTypeConverter)
//  strict private
//    FTypeToken: TTypeToken;
//    {$IFDEF AUTOREFCOUNT}[Weak]{$ENDIF}
//    FConverters: TJsonConverters;
//  strict protected
//    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
//    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
//  protected
//    function GetTypeToken: TTypeToken; override;
//  public
//    constructor Create(const ATypeToken: TTypeToken; AConverters: TJsonConverters);
//  end;
//
//  TJsonRttiValueConverterFactory = class(TInterfacedObject, IJsonConverterFactory)
//  strict private
//    {$IFDEF AUTOREFCOUNT} [Weak] {$ENDIF}
//    FConverters: TJsonConverters;
//  protected
//    property Converters: TJsonConverters read FConverters;
//  strict protected
//    function CreateConverter(const ATypeToken: TTypeToken): IJsonTypeConverter;
//  public
//    constructor Create(AConverters: TJsonConverters);
//  end;

  TJsonEnumConverter = class(TJsonTypeConverter)
  strict private
    FTypeToken: TTypeToken;
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  protected
    function GetTypeToken: TTypeToken; override;
  public
    constructor Create(const ATypeToken: TTypeToken);
  end;

  TJsonValuedEnumConverter = class(TJsonTypeConverter)
  strict private
    FTypeToken: TTypeToken;
    FEnumValues: TList<string>;
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  protected
    function GetTypeToken: TTypeToken; override;
  public
    constructor Create(const ATypeToken: TTypeToken; EnumValues: TList<string>);
  end;

  TJsonEnumConverterFactory = class(TInterfacedObject, IJsonConverterFactory)
  strict protected
    function CreateConverter(Converters: TJsonConverters; const ATypeToken: TTypeToken): IJsonTypeConverter;
  end;

  {$IFDEF DELPHIXE3_LVL}
  TJsonArrayConverter = class(TInterfacedObject, IJsonTypeConverter)
  strict private
    FTypeToken: TTypeToken;
    FElementTypeToken: TTypeToken;
    {$IFDEF AUTOREFCOUNT}[Weak]{$ENDIF}
    FConverters: TJsonConverters;
  strict protected
    procedure WriteJson(const Writer: TJsonWriter; const Value: TValue);
    procedure ReadJson(const Reader: TJsonReader; var Value: TValue);
  public
    constructor Create(const ATypeToken, AElementTypeToken: TTypeToken; AConverters: TJsonConverters);
  end;

  TJsonArrayConverterFactory = class(TInterfacedObject, IJsonConverterFactory)
  strict private
    function GetElementType(ATypeInfo: PTypeInfo): PTypeInfo;
  strict protected
    function CreateConverter(Converters: TJsonConverters; const ATypeToken: TTypeToken): IJsonTypeConverter;
  end;
  {$ENDIF}

  TJsonSetConverter = class(TJsonTypeConverter)
  strict private
    FTypeToken: TTypeToken;
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  protected
    function GetTypeToken: TTypeToken; override;
  public
    constructor Create(const ATypeToken: TTypeToken);
  end;

  TJsonValuedSetConverter = class(TJsonTypeConverter)
  strict private
    FTypeToken: TTypeToken;
    FElementTypeToken: TTypeToken;
    FEnumValues: TList<string>;
  strict protected
    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
  protected
    function GetTypeToken: TTypeToken; override;
  public
    constructor Create(const ATypeToken, AElementTypeToken: TTypeToken; AEnumValues: TList<string>);
  end;

  TJsonSetConverterFactory = class(TInterfacedObject, IJsonConverterFactory)
  strict protected
    function CreateConverter(Converters: TJsonConverters; const ATypeToken: TTypeToken): IJsonTypeConverter;
  end;

  TJsonStringsConverter = class(TInterfacedObject, IJsonTypeConverter)
  strict private
    {$IFDEF AUTOREFCOUNT}[Weak]{$ENDIF}
    FConverters: TJsonConverters;
  public
    procedure WriteJson(const Writer: TJsonWriter; const Value: TValue);
    procedure ReadJson(const Reader: TJsonReader; var Value: TValue);
  public
    constructor Create(AConverters: TJsonConverters);
  end;

  TJsonStringsConverterFactory = class(TInterfacedObject, IJsonConverterFactory)
  strict protected
    function CreateConverter(Converters: TJsonConverters; const ATypeToken: TTypeToken): IJsonTypeConverter;
  end;

//  TJsonArrayConverter = class(TJsonTypeConverter)
//  strict private
//    FTypeToken: TTypeToken;
//  strict protected
//    function TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean; override;
//    function TryReadJson(const Reader: TJsonReader; var Value: TValue): boolean; override;
//  protected
//    function GetTypeToken: TTypeToken; override;
//  public
//    constructor Create(const ATypeToken: TTypeToken);
//  end;

//  TJsonArrayConverterFactory = class(TInterfacedObject, IJsonConverterFactory)
//  strict private
//    {$IFDEF WEAKREF}[Weak]{$ENDIF}
//    FConverters: TJsonConverters;
//  protected
//    property Converters: TJsonConverters read FConverters;
//  strict protected
//    function CreateConverter(const ATypeToken: TTypeToken): IJsonTypeConverter;
//    procedure SetOnObjectProcessed(AObjectProcessed: TProc<TObject>);
//  public
//    constructor Create(AConverters: TJsonConverters);
//  end;

  TJsonPrimitiveConverters = class
  strict private
    class var FFactories: TList<IJsonConverterFactory>;
    class procedure AddFactories(AList: TList<IJsonConverterFactory>);
  public
    class constructor Create;
    class destructor Destroy;
    class function Factories: TEnumerable<IJsonConverterFactory>;
  end;
{$ENDREGION}

implementation

uses
  Variants, FmtBcd, Classes, Bcl.Utils, Bcl.Rtti.Utils,
  Bcl.Json.Attributes,
  Bcl.Json.ObjectConverter,
  Bcl.Json.ElementConverter,
  Bcl.Json.ValueConverter,
  Bcl.Json.ListConverter,
  Bcl.Json.OrderedDictionaryConverter;

{$REGION 'Primitive converters'}
{ TJsonIntegerConverter }

function TJsonInt32Converter<T>.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
begin
  Result := Reader.Peek in [TJsonToken.Number];
  if Result then
    Value := Reader.ReadInteger;
end;

function TJsonInt32Converter<T>.TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean;
begin
  Writer.WriteInteger(Value.AsInteger);
  Result := true;
end;

{ TJsonInt16Converter }

function TJsonInt16Converter<T>.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
begin
  Result := Reader.Peek in [TJsonToken.Number];
  if Result then
    Value := Reader.ReadInteger;
end;

function TJsonInt16Converter<T>.TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean;
begin
  Writer.WriteInteger(Value.AsInteger);
  Result := true;
end;

{ TJsonInt64Converter }

function TJsonInt64Converter<T>.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
begin
  Result := Reader.Peek in [TJsonToken.Number];
  if Result then
    Value := Reader.ReadInt64;
end;

function TJsonInt64Converter<T>.TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean;
begin
  Writer.WriteInteger(Value.AsOrdinal);
  Result := True;
end;

{ TJsonBooleanConverter }

function TJsonBooleanConverter.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
begin
  Result := Reader.Peek in [TJsonToken.Boolean];
  if Result then
    Value := Reader.ReadBoolean;
end;

function TJsonBooleanConverter.TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean;
begin
  Writer.WriteBoolean(Value.AsBoolean);
  Result := true;
end;

{ TJsonDoubleConverter }

function TJsonDoubleConverter<T>.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
begin
  Result := Reader.Peek in [TJsonToken.Number];
  if Result then
    Value := Reader.ReadDouble;
end;

function TJsonDoubleConverter<T>.TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean;
begin
  Writer.WriteDouble(Value.AsExtended);
  Result := True;
end;

{ TJsonDateTimeConverter }

function TJsonDateTimeConverter.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
var
  DateValue: TDateTime;
begin
  Result := (Reader.Peek in [TJsonToken.Text]) and
    TBclUtils.TryISOToDateTime(Reader.ReadString, DateValue);
  if Result then
    TValue.Make(@DateValue, GetTypeToken.TypeInfo, Value);
end;

function TJsonDateTimeConverter.TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean;
var
  Literal: string;
begin
  Literal := TBclUtils.DateTimeToISO(TDateTime(Value.AsType<double>), false);
  Writer.WriteString(Literal);
  Result := True;
end;

{ TJsonTimeConverter }

function TJsonTimeConverter.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
var
  TimeValue: TTime;
begin
  Result := (Reader.Peek in [TJsonToken.Text]) and
    TBclUtils.TryISOToTime(Reader.ReadString, TimeValue);
  if Result then
    TValue.Make(@TimeValue, GetTypeToken.TypeInfo, Value);
end;

function TJsonTimeConverter.TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean;
var
  Literal: string;
begin
  Literal := TBclUtils.TimeToISO(TTime(Value.AsType<double>));
  Writer.WriteString(Literal);
  Result := True;
end;

{ TJsonBinaryConverter }

function TJsonBinaryConverter<T>.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
var
  Bytes: TBytes;
begin
  Result := (Reader.Peek in [TJsonToken.Text]);
  Bytes := TBclUtils.DecodeBase64(Reader.ReadString);
  if Result then
    TValue.Make(@Bytes, GetTypeToken.TypeInfo, Value);
end;

function TJsonBinaryConverter<T>.TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean;
var
  Bytes: TBytes;
  Literal: string;
begin
  Bytes := Value.AsType<TBytes>;
  Literal := TBclUtils.EncodeBase64(Bytes);
  Writer.WriteString(Literal);
  Result := True;
end;

{ TJsonStringConverter<T>  }

function TJsonStringConverter<T>.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
begin
  Result := Reader.Peek in [TJsonToken.Text];
  if Result then
    Value := Reader.ReadString;
end;

function TJsonStringConverter<T>.TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean;
begin
  Writer.WriteString(Value.AsString);
  Result := true;
end;

{ TJsonCharConverter<T> }

function TJsonCharConverter<T>.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
begin
  Result := Reader.Peek in [TJsonToken.Text];
  if Result then
    Value := Reader.ReadString;
end;

function TJsonCharConverter<T>.TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean;
begin
  Writer.WriteString(Value.AsString);
  Result := true;
end;

{ TJsonGuidConverter }

function TJsonGuidConverter.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
var
  GuidValue: TGuid;
begin
  Result := (Reader.Peek in [TJsonToken.Text]) and
    TBclUtils.TryStringToGuid(Reader.ReadString, GuidValue);
  if Result then
    Value := TValue.From<TGuid>(GuidValue);
end;

function TJsonGuidConverter.TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean;
var
  Literal: string;
begin
  Literal := Copy(GuidToString(Value.AsType<TGuid>), 2, 36);
  Writer.WriteString(Literal);
  Result := True;
end;

{ TJsonCurrencyConverter }

function TJsonCurrencyConverter.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
begin
  Result := Reader.Peek in [TJsonToken.Number];
  if Result then
    Value := Reader.ReadDouble;
end;

function TJsonCurrencyConverter.TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean;
begin
  Writer.WriteDouble(Value.AsCurrency);
  Result := True;
end;

{ TJsonEnumConverter }

constructor TJsonEnumConverter.Create(const ATypeToken: TTypeToken);
begin
  FTypeToken := ATypeToken;
end;

function TJsonEnumConverter.GetTypeToken: TTypeToken;
begin
  Result := FTypeToken;
end;

function TJsonEnumConverter.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
var
  IntValue: Integer;
begin
  Result := Reader.Peek in [TJsonToken.Text];
  if Result then
  begin
    IntValue := GetEnumValue(GetTypeToken.TypeInfo, Reader.ReadString);
    Result := IntValue >= 0;
    if Result then
      Value := TValue.FromOrdinal(GetTypeToken.TypeInfo, IntValue);
  end;
end;

function TJsonEnumConverter.TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean;
begin
  Result := True;
  Writer.WriteString(GetEnumName(GetTypeToken.TypeInfo, Value.AsOrdinal));
end;

{ TJsonEnumConverterFactory }

function TJsonEnumConverterFactory.CreateConverter(Converters: TJsonConverters;
  const ATypeToken: TTypeToken): IJsonTypeConverter;
var
  Attr: JsonEnumValuesAttribute;
  Context: TRttiContext;
  RttiType: TRttiType;
begin
  if (ATypeToken.TypeInfo.Kind = tkEnumeration) then
  begin
    Context := TRttiContext.Create;
    try
      RttiType := Context.GetType(ATypeToken);
      if (RttiType <> nil) and TRttiUtils.GetInstance.TryGetAttribute<JsonEnumValuesAttribute>(RttiType, Attr) then
        Result := TJsonValuedEnumConverter.Create(ATypeToken, Attr.EnumValues)
      else
        Result := TJsonEnumConverter.Create(ATypeToken)
    finally
      Context.Free;
    end;
  end
  else
    Result := nil;
end;

{ TJsonDateConverter }

function TJsonDateConverter.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
var
  DateValue: TDate;
begin
  Result := (Reader.Peek in [TJsonToken.Text]) and
    TBclUtils.TryISOToDate(Reader.ReadString, DateValue);
  if Result then
    TValue.Make(@DateValue, GetTypeToken.TypeInfo, Value);
end;

function TJsonDateConverter.TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean;
var
  Literal: string;
begin
  Literal := TBclUtils.DateToISO(TDate(Value.AsType<double>));
  Writer.WriteString(Literal);
  Result := True;
end;

{ TJsonVariantConverter }

function TJsonVariantConverter.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
var
  V: Variant;
begin
  case Reader.Peek of
    TJsonToken.Boolean:
      V := Reader.ReadBoolean;
    TJsonToken.Text:
      V := Reader.ReadString;
    TJsonToken.Number:
      if Reader.IsFloat then
        V := Reader.ReadDouble
      else
      if Reader.IsInt64 then
        V := Reader.ReadInt64
      else
        V := Reader.ReadInteger;
    TJsonToken.Null:
      V := NULL;
  else
    Exit(False);
  end;
  Result := True;
  TValue.Make(@V, GetTypeToken.TypeInfo, Value);
end;

function TJsonVariantConverter.TryWriteJson(const Writer: TJsonWriter;
  const Value: TValue): boolean;
var
  V: Variant;
  VarData: TVarData;
begin
  V := Value.AsType<Variant>;
  VarData := TVarData(V);
  Result := True;
  case VarType(V) of
    varEmpty, varNull:
      Writer.WriteNull;
    varBoolean:
      Writer.WriteBoolean(VarData.VBoolean);
    varShortInt:
      Writer.WriteInteger(VarData.VShortInt);
    varSmallint:
      Writer.WriteInteger(VarData.VSmallInt);
    varInteger:
      Writer.WriteInteger(VarData.VInteger);
    varSingle:
      Writer.WriteDouble(VarData.VSingle);
    varDouble:
      Writer.WriteDouble(VarData.VDouble);
    varCurrency:
      Writer.WriteDouble(VarData.VCurrency);
    varDate:
      Writer.WriteDouble(VarData.VDate);
    varOleStr:
      Writer.WriteString(string(VarData.VOleStr));
    varError:
      Writer.WriteInteger(VarData.VError);
    varByte:
      Writer.WriteInteger(VarData.VByte);
    varWord:
      Writer.WriteInteger(VarData.VWord);
    varLongWord:
      Writer.WriteInteger(VarData.VLongWord);
    varInt64:
      Writer.WriteInteger(VarData.VInt64);
    varUInt64:
      Writer.WriteInteger(VarData.VUInt64);
    varString:
      {$IFNDEF NEXTGEN}
      Writer.WriteString(string(AnsiString(VarData.VString)));
      {$ELSE}
      Writer.WriteString(string(VarData.VString));
      {$ENDIF}
    varUString:
      Writer.WriteString(UnicodeString(VarData.VUString));
  else
    if VarIsFMTBcd(V) then
      Writer.WriteDouble(BcdToDouble(VarToBcd(V)))
    else
      Result := False;
  end;
end;

//{ TJsonArrayConverter }
//
//constructor TJsonArrayConverter.Create(const ATypeToken: TTypeToken);
//begin
//  FTypeToken := ATypeToken;
//end;
//
//function TJsonArrayConverter.GetTypeToken: TTypeToken;
//begin
//  Result := FTypeToken;
//end;
//
//function TJsonArrayConverter.TryReadJson(const Reader: TJsonReader;
//  var Value: TValue): boolean;
//var
//  IntValue: integer;
//begin
//  Result := Reader.Peek in [TJsonToken.Text];
//  if Result then
//  begin
//    IntValue := GetEnumValue(GetTypeToken.TypeInfo, Reader.ReadString);
//    Result := IntValue >= 0;
//    if Result then
//      Value := TValue.FromOrdinal(GetTypeToken.TypeInfo, IntValue);
//  end;
//end;
//
//function TJsonArrayConverter.TryWriteJson(const Writer: TJsonWriter;
//  const Value: TValue): boolean;
//begin
//  Writer.WriteString(GetEnumName(GetTypeToken.TypeInfo, Value.AsOrdinal));
//  Result := true;
//end;

{ TJsonArrayConverterFactory }

//function TJsonArrayConverterFactory.CreateConverter(
//  const ATypeToken: TTypeToken): IJsonTypeConverter;
//var
//  ItemType: PTypeInfo;
//begin
//  if (ATypeToken.TypeInfo.Kind in [tkArray, tkDynArray]) then
//  begin
//    ItemType := System.TypInfo.GetTypeData(ATypeToken.TypeInfo)^.ArrayData.ElType^;
//    Result := TJsonArrayConverter.Create(ATypeToken,
//      TTypeToken.FromTypeInfo(ItemType), Self);
//  end
//  else
//    Result := nil;
//end;
//
//procedure TJsonArrayConverterFactory.SetOnObjectProcessed(
//  AObjectProcessed: TProc<TObject>);
//begin
//end;

{ TJsonRealConverter }

function TJsonRealConverter.GetTypeToken: TTypeToken;
begin
  // This is only necessary to workaround a bug in Linux compiler.
  // TypeInfo(T) was returning wrong information
  // (type info for "Double" type instead of "Real" type)
  Result := TypeInfo(Real);
end;

{ TJsonStringsConverter }

constructor TJsonStringsConverter.Create(AConverters: TJsonConverters);
begin
  inherited Create;
  FConverters := AConverters;
end;

procedure TJsonStringsConverter.ReadJson(const Reader: TJsonReader;
  var Value: TValue);
var
  WasCreated: Boolean;
  Strings: TStrings;
begin
  if Value.IsEmpty then
  begin
    Strings := TStringList.Create;
    WasCreated := True;
  end
  else
  begin
    Strings := Value.AsType<TStrings>;
    WasCreated := False;
  end;

  try
    Strings.Clear;
    if not Reader.IsNull then
    begin
      Reader.ReadBeginArray;
      while Reader.HasNext do
        Strings.Add(Reader.ReadString);
      Reader.ReadEndArray;
    end;

    if WasCreated then
    begin
      FConverters.ObjectCreated(Strings);
      Value := Strings;
    end;
  except
    if WasCreated then
      Strings.Free;
    raise;
  end;
end;

procedure TJsonStringsConverter.WriteJson(const Writer: TJsonWriter;
  const Value: TValue);
var
  I: Integer;
  Strings: TStrings;
begin
  Strings := Value.AsType<TStrings>;
  Writer.WriteBeginArray;
  for I := 0 to Strings.Count - 1 do
    Writer.WriteString(Strings[I]);
  Writer.WriteEndArray;
end;

{ TJsonStringsConverterFactory }

function TJsonStringsConverterFactory.CreateConverter(Converters: TJsonConverters;
  const ATypeToken: TTypeToken): IJsonTypeConverter;
begin
  if ATypeToken.IsClass and ATypeToken.GetClass.InheritsFrom(TStrings) then
    Result := TJsonStringsConverter.Create(Converters)
  else
    Result := nil;
end;

{ TJsonPrimitiveConverters }

class procedure TJsonPrimitiveConverters.AddFactories(
  AList: TList<IJsonConverterFactory>);
begin
  // Most common types
  AList.Add(TJsonConverterStaticFactory<TJsonStringConverter>.Factory);
  AList.Add(TJsonConverterStaticFactory<TJsonIntegerConverter>.Factory);
  AList.Add(TJsonConverterStaticFactory<TJsonBooleanConverter>.Factory);
  AList.Add(TJsonConverterStaticFactory<TJsonDoubleConverter>.Factory);
  AList.Add(TJsonConverterStaticFactory<TJsonDateTimeConverter>.Factory);
  AList.Add(TJsonEnumConverterFactory.Create);

  // Integer types
  AList.Add(TJsonConverterStaticFactory<TJsonInt64Converter>.Factory);
  AList.Add(TJsonConverterStaticFactory<TJsonLongintConverter>.Factory);
  AList.Add(TJsonConverterStaticFactory<TJsonCardinalConverter>.Factory);
  AList.Add(TJsonConverterStaticFactory<TJsonLongwordConverter>.Factory);
  AList.Add(TJsonConverterStaticFactory<TJsonWordConverter>.Factory);
  AList.Add(TJsonConverterStaticFactory<TJsonShortintConverter>.Factory);
  AList.Add(TJsonConverterStaticFactory<TJsonByteConverter>.Factory);
  AList.Add(TJsonConverterStaticFactory<TJsonSmallIntConverter>.Factory);

  // Float types
  AList.Add(TJsonConverterStaticFactory<TJsonRealConverter>.Factory);
  AList.Add(TJsonConverterStaticFactory<TJsonSingleConverter>.Factory);
  AList.Add(TJsonConverterStaticFactory<TJsonExtendedConverter>.Factory);
  AList.Add(TJsonConverterStaticFactory<TJsonCurrencyConverter>.Factory);

  // Date time types
  AList.Add(TJsonConverterStaticFactory<TJsonDateConverter>.Factory);
  AList.Add(TJsonConverterStaticFactory<TJsonTimeConverter>.Factory);

  // Native structure types
  AList.Add(TJsonSetConverterFactory.Create);

  // Guid types
  AList.Add(TJsonConverterStaticFactory<TJsonGuidConverter>.Factory);

  // String types
  {$IFNDEF NEXTGEN}
  AList.Add(TJsonConverterStaticFactory<TJsonAnsiStringConverter>.Factory);
  AList.Add(TJsonConverterStaticFactory<TJsonWideStringConverter>.Factory);
  {$ENDIF}

  // Char types
  AList.Add(TJsonConverterStaticFactory<TJsonCharConverter>.Factory);
  {$IFNDEF NEXTGEN}
  AList.Add(TJsonConverterStaticFactory<TJsonAnsiCharConverter>.Factory);
  AList.Add(TJsonConverterStaticFactory<TJsonWideCharConverter>.Factory);
  {$ENDIF}

  // Variant type
  AList.Add(TJsonConverterStaticFactory<TJsonVariantConverter>.Factory);
end;

class constructor TJsonPrimitiveConverters.Create;
begin
  FFactories := TList<IJsonConverterFactory>.Create;
  AddFactories(FFactories);
end;

class destructor TJsonPrimitiveConverters.Destroy;
begin
  FFactories.Free;
end;

class function TJsonPrimitiveConverters.Factories: TEnumerable<IJsonConverterFactory>;
begin
  Result := FFactories;
end;
{$ENDREGION}

{ TTypeToken }

constructor TTypeToken.Create(const ATypeInfo: PTypeInfo);
begin
  Create(ATypeInfo, nil);
end;

function TTypeToken.ClassFromTypeInfo(ATypeInfo: PTypeInfo): TClass;
begin
  if IsClass then
    Result := GetTypeData(TypeInfo)^.ClassType
  else
    Result := nil;
end;

constructor TTypeToken.Create(const ATypeInfo, AItemTypeInfo: PTypeInfo);
begin
  FTypeInfo := ATypeInfo;
  FItemTypeInfo := AItemTypeInfo;
end;

class operator TTypeToken.Equal(const Left, Right: TTypeToken): Boolean;
begin
  Result := (Left.TypeInfo = Right.TypeInfo) and (Left.ItemTypeInfo = Right.ItemTypeInfo);
end;

class function TTypeToken.FromClass(const AClass: TClass): TTypeToken;
begin
  Result := FromTypeInfo(AClass.ClassInfo);
end;

class function TTypeToken.FromTypeInfo(const ATypeInfo: PTypeInfo): TTypeToken;
begin
  Result := TTypeToken.Create(ATypeInfo);
end;

function TTypeToken.GetClass: TClass;
begin
  Result := ClassFromTypeInfo(TypeInfo);
end;

function TTypeToken.GetItemClass: TClass;
begin
  Result := ClassFromTypeInfo(ItemTypeInfo);
end;

class operator TTypeToken.Implicit(ASource: PTypeInfo): TTypeToken;
begin
  Result := TTypeToken.FromTypeInfo(ASource);
end;

class operator TTypeToken.Implicit(ASource: TTypeToken): PTypeInfo;
begin
  Result := ASource.TypeInfo;
end;

class operator TTypeToken.Implicit(ASource: TClass): TTypeToken;
begin
  Result := TTypeToken.FromClass(ASource);
end;

function TTypeToken.IsClass: Boolean;
begin
  Result := (TypeInfo <> nil) and (TypeInfo.Kind = tkClass);
end;

{ TJsonConverterStaticFactory<T, C> }

class destructor TJsonConverterStaticFactory<C>.Destroy;
begin
  FFactory := nil;
end;

class function TJsonConverterStaticFactory<C>.GetFactory: IJsonConverterFactory;
var
  Converter: IJsonTypeConverter;
  ConverterObj: TJsonTypeConverter;
begin
  if FFactory = nil then
  begin
    ConverterObj := C.Create;
    Converter := ConverterObj;
    FFactory := TJsonConverterStaticFactory.Create(ConverterObj.GetTypeToken, Converter);
  end;
  Result := FFactory;
end;

{ TJsonConverterFactory }

constructor TJsonConverterStaticFactory.Create(const ATypeToken: TTypeToken;
  AConverter: IJsonTypeConverter);
begin
  FTypeToken := ATypeToken;
  FConverter := AConverter;
end;

function TJsonConverterStaticFactory.CreateConverter(Converters: TJsonConverters;
  const ATypeToken: TTypeToken): IJsonTypeConverter;
begin
  if FTypeToken = ATypeToken then
    Result := FConverter
  else
    Result := nil;
end;

{ TJsonConverters }

procedure TJsonConverters.AddFactory(AFactory: IJsonConverterFactory);
begin
  FCustomFactories.Add(AFactory);
end;

procedure TJsonConverters.ConverterCreated(const ATypeToken: TTypeToken;
  Converter: IJsonTypeConverter);
begin
  if Assigned(FOnConverterCreated) then
    FOnConverterCreated(ATypeToken, Converter);
end;

constructor TJsonConverters.Create;
begin
  FConverters := TObjectDictionary<TTypeToken, IJsonTypeConverter>.Create();
  FCustomFactories := TList<IJsonConverterFactory>.Create;
  FCustomFactories.Add(TJsonElementConverterFactory.Create);

  {$IFDEF DELPHIXE6_LVL}
  FCustomFactories.Add(TJsonValueConverterFactory.Create);
  {$ENDIF}
  FCustomFactories.Add(TJsonStringsConverterFactory.Create);
  FCustomFactories.Add(TJsonListConverterFactory.Create);
  FCustomFactories.Add(TJsonOrderedDictionaryConverterFactory.Create);

  // Array of byte: should be in primite converters, but we need to move them
  // here so TArray<byte> factory is called before generic TArray<T>
  FCustomFactories.Add(TJsonConverterStaticFactory<TJsonBytesConverter>.Factory);
  FCustomFactories.Add(TJsonConverterStaticFactory<TJsonArrayByteConverter>.Factory);

  {$IFDEF DELPHIXE3_LVL}
  FCustomFactories.Add(TJsonArrayConverterFactory.Create);
  {$ENDIF}
//  FCustomFactories.Add(TJsonRttiValueConverterFactory.Create(Self));
  FObjectFactory := TObjectFactory.Create;
  FObjectConverterFactory := TJsonObjectConverterFactory.Create;
  FObjectConverterFactoryIntf := FObjectConverterFactory;
end;

//class function TJsonConverters.Default: TJsonConverters;
//begin
//  if FDefault = nil then
//  begin
//    FDefault := TJsonConverters.Create;
//  end;
//  Result := FDefault;
//end;

destructor TJsonConverters.Destroy;
begin
  FConverters.Free;
  FCustomFactories.Free;
  inherited;
end;

//class destructor TJsonConverters.Destroy;
//begin
//  if FDefault <> nil then
//  begin
//    FDefault.Free;
//    FDefault := nil;
//  end;
//end;

function TJsonConverters.Find(const ATypeToken: TTypeToken): IJsonTypeConverter;
var
  Factory: IJsonConverterFactory;
begin
  if FConverters.TryGetValue(ATypeToken, Result) then
    Exit;

  Result := nil;

  for Factory in FCustomFactories do
    if InternalCreateConverter(Factory, ATypeToken, Result) then Exit;

  for Factory in TJsonPrimitiveConverters.Factories do
    if InternalCreateConverter(Factory, ATypeToken, Result) then Exit;

  if InternalCreateConverter(FObjectConverterFactoryIntf, ATypeToken, Result) then Exit;
end;

function TJsonConverters.Get(const ATypeToken: TTypeToken): IJsonTypeConverter;
begin
  Result := Find(ATypeToken);
  if Result = nil then
    raise EJsonConverterNotFound.Create(ATypeToken);
end;

function TJsonConverters.GetObjectConverterFactory: TJsonAbstractObjectConverterFactory;
begin
  Result := FObjectConverterFactory;
end;

function TJsonConverters.InternalCreateConverter(Factory: IJsonConverterFactory;
  const ATypeToken: TTypeToken; out Converter: IJsonTypeConverter): Boolean;
begin
  Converter := Factory.CreateConverter(Self, ATypeToken);
  Result := Converter <> nil;
  if Result then
  begin
    ConverterCreated(ATypeToken, Converter);
    FConverters.Add(ATypeToken, Converter);
  end;
end;

procedure TJsonConverters.ObjectCreated(Value: TObject);
begin
  if Assigned(FOnObjectProcessed) then
    FOnObjectProcessed(Value);
end;

procedure TJsonConverters.ObjectWritten(Value: TObject);
begin
  if Assigned(FOnObjectProcessed) then
    FOnObjectProcessed(Value);
end;

procedure TJsonConverters.SetOnObjectProcessed(const Value: TProc<TObject>);
begin
  FOnObjectProcessed := Value;
end;

{ TJsonConverters.ETypeConverterNotFound }

constructor TJsonConverters.ETypeConverterNotFound.Create(const ATypeToken: TTypeToken);
begin
  inherited CreateFmt('Could not find converter for type "%s"', [GetTypeName(ATypeToken.TypeInfo)]);
end;

{ TJsonTypeConverter }

procedure TJsonTypeConverter.ReadJson(const Reader: TJsonReader; var Value: TValue);
var
  TempValue: TValue;
begin
  if Reader.IsNull then
  begin
    Value := TValue.Empty;
    Reader.ReadNull;
  end
  else
  begin
    TempValue := Value;
    if not (TryReadJson(Reader, TempValue) and TempValue.TryCast(Self.GetTypeToken.TypeInfo, Value)) then
      raise EConvertFromJsonError.Create(GetTypeName(Self.GetTypeToken.TypeInfo));
  end;
end;

function TJsonTypeConverter.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
begin
  Result := False;
end;

function TJsonTypeConverter.TryWriteJson(const Writer: TJsonWriter; const Value: TValue): boolean;
begin
  Result := False;
end;

procedure TJsonTypeConverter.WriteJson(const Writer: TJsonWriter; const Value: TValue);
begin
  if Value.IsEmpty then
    Writer.WriteNull
  else
  begin
    if not TryWriteJson(Writer, Value) then
      raise EConvertToJsonError.Create(GetTypeName(Self.GetTypeToken.TypeInfo), Value);
  end;
end;

{ TJsonTypeConverter<T> }

function TJsonTypeConverter<T>.GetTypeToken: TTypeToken;
begin
  Result := TTypeToken.FromTypeInfo(TypeInfo(T));
end;

{ TJsonTypeConverter.EConvertToJsonError }

constructor TJsonTypeConverter.EConvertToJsonError.Create(const TypeName: string;
  Value: TValue);
begin
  inherited CreateFmt('Cannot serialize property of type "%s", with value %s, to Json format',
    [TypeName, Value.ToString]);
end;

{ TJsonTypeConverter.EConvertFromJsonError }

constructor TJsonTypeConverter.EConvertFromJsonError.Create(const TypeName: string);
begin
  inherited CreateFmt('Cannot read JSON property of type "%s", invalid value.', [TypeName]);
end;

{ EJsonConverterNotFound }

constructor EJsonConverterNotFound.Create(const ATypeToken: TTypeToken);
begin
  inherited CreateFmt('Could not find JSON converter for type "%s"',
    [GetTypeName(ATypeToken.TypeInfo)]);
end;

{ TJsonSetConverterFactory }

function TJsonSetConverterFactory.CreateConverter(Converters: TJsonConverters;
  const ATypeToken: TTypeToken): IJsonTypeConverter;
var
  Attr: JsonEnumValuesAttribute;
  Context: TRttiContext;
  RttiType: TRttiSetType;
begin
  Result := nil;
  if (ATypeToken.TypeInfo.Kind = tkSet) then
  begin
    Context := TRttiContext.Create;
    try
      RttiType := Context.GetType(ATypeToken).AsSet;
      if (RttiType <> nil) and (RttiType.ElementType <> nil) then
      begin
        if TRttiUtils.GetInstance.TryGetAttribute<JsonEnumValuesAttribute>(RttiType.ElementType, Attr) then
          Result := TJsonValuedSetConverter.Create(ATypeToken, RttiType.ElementType.Handle, Attr.EnumValues)
        else
          Result := TJsonSetConverter.Create(ATypeToken);
      end;
    finally
      Context.Free;
    end;
  end;
end;

{ TJsonSetConverter }

constructor TJsonSetConverter.Create(const ATypeToken: TTypeToken);
begin
  FTypeToken := ATypeToken;
end;

function TJsonSetConverter.GetTypeToken: TTypeToken;
begin
  Result := FTypeToken;
end;

function TJsonSetConverter.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
var
  Str: string;
begin
  Str := '';
  Reader.ReadBeginArray;
  while Reader.HasNext do
  begin
    if Str <> '' then
      Str := Str + ',';
    Str := Str + Reader.ReadString;
  end;
  Reader.ReadEndArray;
  TValue.Make(StringToSet(GetTypeToken.TypeInfo, Str), GetTypeToken.TypeInfo, Value);
  Result := True;
end;

function TJsonSetConverter.TryWriteJson(const Writer: TJsonWriter;
  const Value: TValue): boolean;

  function NextWord(var P: PChar): string;
  var
    I: Integer;
  begin
    I := 0;
    while (P[I] <> #0) and (P[I] <> ',') do
      Inc(I);
    SetString(Result, P, I);
    while (P[I] = ',') do
      Inc(I);
    Inc(P, I);
  end;

var
  Str, EnumName: string;
  P: PChar;
begin
  Str := SetToString(GetTypeToken.TypeInfo, PInteger(Value.GetReferenceToRawData)^);
  Writer.WriteBeginArray;
  P := PChar(Str);
  EnumName := NextWord(P);
  while EnumName <> '' do
  begin
    Writer.WriteString(EnumName);
    EnumName := NextWord(P);
  end;
  Writer.WriteEndArray;
  Result := true;
end;


{ TJsonArrayConverter }

{$IFDEF DELPHIXE3_LVL}
constructor TJsonArrayConverter.Create(const ATypeToken, AElementTypeToken: TTypeToken;
  AConverters: TJsonConverters);
begin
  FTypeToken := ATypeToken;
  FConverters := AConverters;
  FElementTypeToken := AElementTypeToken;
end;

procedure TJsonArrayConverter.ReadJson(const Reader: TJsonReader;
  var Value: TValue);

  procedure SetCapacity(Size: NativeInt);
  begin
    DynArraySetLength(PPointer(Value.GetReferenceToRawData)^, Value.TypeInfo, 1, @Size);
  end;

  procedure InternalGrow(var NewCount: NativeInt; ANewCount: NativeInt);
  begin
    if NewCount = 0 then
      NewCount := ANewCount
    else
      repeat
        NewCount := NewCount * 2;
        if NewCount < 0 then
          OutOfMemoryError;
      until NewCount >= ANewCount;
    SetCapacity(NewCount);
  end;

var
  I, Size: NativeInt;
  ElementValue: TValue;
begin
  TValue.Make(nil, FTypeToken.TypeInfo, Value);
  Reader.ReadBeginArray;
  I := 0;
  Size := 0;
  while Reader.HasNext do
  begin
    if Size <= I then
      InternalGrow(Size, I + 1);

    ElementValue := TValue.Empty;
    FConverters.Get(FElementTypeToken).ReadJson(Reader, ElementValue);
    Value.SetArrayElement(I, ElementValue);
    Inc(I);
  end;
  if Size <> I then
    SetCapacity(I);
  Reader.ReadEndArray;
end;

procedure TJsonArrayConverter.WriteJson(const Writer: TJsonWriter;
  const Value: TValue);
var
  I: Integer;
  Element: TValue;
begin
  Writer.WriteBeginArray;
  for I := 0 to Value.GetArrayLength - 1 do
  begin
    Element := Value.GetArrayElement(I);
    FConverters.Get(Element.TypeInfo).WriteJson(Writer, Element);
  end;
  Writer.WriteEndArray;
end;

{ TJsonArrayConverterFactory }

function TJsonArrayConverterFactory.CreateConverter(Converters: TJsonConverters;
  const ATypeToken: TTypeToken): IJsonTypeConverter;
var
  ElementType: PTypeInfo;
begin
  Result := nil;
  if ATypeToken.TypeInfo.Kind = tkDynArray then
  begin
    ElementType := GetElementType(ATypeToken.TypeInfo);
    if ElementType <> nil then
      Result := TJsonArrayConverter.Create(ATypeToken, ElementType, Converters);
  end;
end;

function TJsonArrayConverterFactory.GetElementType(
  ATypeInfo: PTypeInfo): PTypeInfo;
var
  ref: PPTypeInfo;
begin
  ref := GetTypeData(ATypeInfo).DynArrElType;
  if ref = nil then
    Result := nil
  else
    Result := ref^;
end;
{$ENDIF}

//{ TJsonRttiValueConverterFactory }
//
//constructor TJsonRttiValueConverterFactory.Create(AConverters: TJsonConverters);
//begin
//  FConverters := AConverters;
//end;
//
//function TJsonRttiValueConverterFactory.CreateConverter(
//  const ATypeToken: TTypeToken): IJsonTypeConverter;
//begin
//  if ATypeToken.TypeInfo = TypeInfo(TValue) then
//    Result := TJsonRttiValueConverter.Create(ATypeToken, FConverters)
//  else
//    Result := nil;
//end;
//
//{ TJsonRttiValueConverter }
//
//constructor TJsonRttiValueConverter.Create(const ATypeToken: TTypeToken;
//  AConverters: TJsonConverters);
//begin
//  FTypeToken := ATypeToken;
//  FConverters := AConverters;
//end;
//
//function TJsonRttiValueConverter.GetTypeToken: TTypeToken;
//begin
//  Result := FTypeToken;
//end;
//
//function TJsonRttiValueConverter.TryReadJson(const Reader: TJsonReader;
//  var Value: TValue): boolean;
//begin
//
//end;
//
//function TJsonRttiValueConverter.TryWriteJson(const Writer: TJsonWriter;
//  const Value: TValue): boolean;
//var
//  InnerValue: TValue;
//begin
//  Result := True;
//  InnerValue := Value.AsType<TValue>;
//  FConverters.Get(InnerValue.TypeInfo).WriteJson(Writer, InnerValue);
//end;

{ TJsonValuedEnumConverter }

constructor TJsonValuedEnumConverter.Create(const ATypeToken: TTypeToken;
  EnumValues: TList<string>);
begin
  FTypeToken := ATypeToken;
  FEnumValues := EnumValues;
end;

function TJsonValuedEnumConverter.GetTypeToken: TTypeToken;
begin
  Result := FTypeToken;
end;

function TJsonValuedEnumConverter.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
var
  IntValue: Integer;
  StringValue: string;
begin
  Result := Reader.Peek in [TJsonToken.Text];
  if Result then
  begin
    StringValue := Reader.ReadString;
    for IntValue := 0 to FEnumValues.Count - 1 do
      if FEnumValues[IntValue] = StringValue then
        break;
    Result := IntValue < FEnumValues.Count;
    if Result then
      Value := TValue.FromOrdinal(GetTypeToken.TypeInfo, IntValue);
  end;
end;

function TJsonValuedEnumConverter.TryWriteJson(const Writer: TJsonWriter;
  const Value: TValue): boolean;
var
  EnumValue: Integer;
begin
  Result := True;
  EnumValue := Value.AsOrdinal;
  if (EnumValue >= 0) and (EnumValue < FEnumValues.Count) then
    Writer.WriteString(FEnumValues[EnumValue])
  else
    Result := False;
end;

{ TJsonValuedSetConverter }

constructor TJsonValuedSetConverter.Create(const ATypeToken,
  AElementTypeToken: TTypeToken; AEnumValues: TList<string>);
begin
  FTypeToken := ATypeToken;
  FElementTypeToken := AElementTypeToken;
  FEnumValues := AEnumValues;
end;

function TJsonValuedSetConverter.GetTypeToken: TTypeToken;
begin
  Result := FTypeToken;
end;

function TJsonValuedSetConverter.TryReadJson(const Reader: TJsonReader;
  var Value: TValue): boolean;
var
  Str: string;
  EnumValue: Integer;
begin
  Str := '';
  Reader.ReadBeginArray;
  while Reader.HasNext do
  begin
    if Str <> '' then
      Str := Str + ',';
    EnumValue := FEnumValues.IndexOf(Reader.ReadString);
    Str := Str + GetEnumName(FElementTypeToken.TypeInfo, EnumValue);
  end;
  Reader.ReadEndArray;
  TValue.Make(StringToSet(GetTypeToken.TypeInfo, Str), GetTypeToken.TypeInfo, Value);
  Result := True;
end;

function TJsonValuedSetConverter.TryWriteJson(const Writer: TJsonWriter;
  const Value: TValue): boolean;

  function NextWord(var P: PChar): string;
  var
    I: Integer;
  begin
    I := 0;
    while (P[I] <> #0) and (P[I] <> ',') do
      Inc(I);
    SetString(Result, P, I);
    while (P[I] = ',') do
      Inc(I);
    Inc(P, I);
  end;

var
  Str, EnumName: string;
  P: PChar;
  I: Integer;
begin
  Str := SetToString(GetTypeToken.TypeInfo, PInteger(Value.GetReferenceToRawData)^);
  Writer.WriteBeginArray;
  P := PChar(Str);
  EnumName := NextWord(P);
  while EnumName <> '' do
  begin
    I := GetEnumValue(FElementTypeToken.TypeInfo, EnumName);
    if (I < 0) or (I >= FEnumValues.Count) then Exit(False);
    Writer.WriteString(FEnumValues[I]);
    EnumName := NextWord(P);
  end;
  Writer.WriteEndArray;
  Result := true;
end;

end.

