2017-02-10 27 views
6

표준 델파이 시리얼 라이저를 사용하여 표준 델파이 컨테이너를 직렬화/비 직렬화하려고합니다.deserialized TDictionary가 제대로 작동하지 않는 이유는 무엇입니까?

procedure TForm7.TestButtonClick(Sender: TObject); 
var 
    dict: TDictionary<Integer, Integer>; 
    jsonValue: TJSONValue; 
begin 
    //serialization 
    dict := TDictionary<Integer, Integer>.Create; 
    dict.Add(1, 1); 
    jsonValue := TJsonConverter.ObjectToJSON(dict); 
    dict.Free; 

    //deserialization 
    dict := TJsonConverter.JSONToObject(jsonValue) as TDictionary<Integer, Integer>; 
    try 
     Assert(dict.ContainsKey(1), 'deserialization error - key not found'); 
    except 
     Assert(false, 'deserialization error - dict object broken'); 
    end; 
end; 

개체를 JSON으로 또는 그 반대로 변환하는 방법이 있습니다.

class function TJsonConverter.JSONToObject(AJSONValue: TJSONValue): TObject; 
var 
    lUnMarshal: TJSONUnMarshal; 
begin 
    lUnMarshal := TJSONUnMarshal.Create(); 
    try 
     Result := lUnMarshal.Unmarshal(AJSONValue); 
    finally 
     lUnMarshal.Free; 
    end; 
end; 

class function TJsonConverter.ObjectToJSON(AData: TObject): TJSONValue; 
var 
    lMarshal: TJSONMarshal; 
begin 
    lMarshal := TJSONMarshal.Create(); 

    try 
     Result := lMarshal.Marshal(AData); 
    finally 
     lMarshal.Free; 
    end; 
end; 

라인 :

dict := TJsonConverter.JSONToObject(jsonValue) as TDictionary<Integer, Integer>; 

가 제대로 사전을 만들지 않습니다. [ Dictionary created correctly[1]

여기 직렬화에 의해 DICT 작성 : 여기 은 DICT 생성자로 만드는 모습입니다 Dictionary deserialized wrong

내가 그것을 어떻게 해결할 수 있습니까?

편집

: 여기 이 문제는 TJSONMarshal는 RTTI를 사용하여 사전을 인스턴스화된다는 것이다 JSON 내용

{ 
     "type" : "System.Generics.Collections.TDictionary<System.Integer,System.Integer>", 
     "id" : 1, 
     "fields" : { 
      "FItems" : [ 
      [ -1, 0, 0 ], 
      [ -1, 0, 0 ], 
      [ -1, 0, 0 ], 
      [ 911574339, 1, 1 ] 
      ], 
      "FCount" : 1, 
      "FGrowThreshold" : 3, 
      "FKeyCollection" : null, 
      "FValueCollection" : null 
     } 
    } 
+0

당신은 JSON 컨텐츠를 추가 할 수 있습니까? – mjn

답변

9

입니다. 그것은 찾을 수있는 첫 번째 매개 변수없는 생성자를 호출하여이를 수행합니다. 그리고 슬프게도, 그것은 TObject에 정의 된 생성자입니다.

TDictionary<K,V>에 선언 된 생성자를 살펴 보겠습니다. 적어도 내 XE7 버전에는 다음과 같습니다.

constructor Create(ACapacity: Integer = 0); overload; 
constructor Create(const AComparer: IEqualityComparer<TKey>); overload; 
constructor Create(ACapacity: Integer; const AComparer: IEqualityComparer<TKey>); overload; 
constructor Create(const Collection: TEnumerable<TPair<TKey,TValue>>); overload; 
constructor Create(const Collection: TEnumerable<TPair<TKey,TValue>>; 
    const AComparer: IEqualityComparer<TKey>); overload; 

이러한 생성자에는 모두 매개 변수가 있습니다.

는 당신이

TDictionary<Integer, Integer>.Create 

를 작성하고 할당 FComparer와 인스턴스를 만들 수 있다는 사실에 속지 마십시오. 위의 첫 번째 오버로드가 해결되면 컴파일러에서 해당 코드를

TDictionary<Integer, Integer>.Create(0) 

으로 기본 매개 변수로 다시 씁니다.

당신이해야 할 일은 클래스를 적절하게 인스턴스화하는 매개 변수없는 생성자가있는 클래스 만 사용해야한다는 것입니다. 죄송합니다. TDictionary<K,V>이 청구서에 맞지 않습니다.

그러나 매개 변수없는 생성자를 소개하는 하위 클래스를 파생시킬 수 있으며 코드는 해당 클래스에서 작동해야합니다.

다음 코드는 보여줍니다

{$APPTYPE CONSOLE} 

uses 
    System.SysUtils, 
    System.Generics.Collections, 
    System.Rtti; 

type 
    TDictionary<K,V> = class(System.Generics.Collections.TDictionary<K,V>) 
    public 
    constructor Create; 
    end; 

{ TDictionary<K, V> } 

constructor TDictionary<K, V>.Create; 
begin 
    inherited Create(0); 
end; 

type 
    TInstance<T: class> = class 
    class function Create: T; static; 
    end; 

class function TInstance<T>.Create: T; 
// mimic the way that your JSON marshalling code instantiates objects 
var 
    ctx: TRttiContext; 
    typ: TRttiType; 
    mtd: TRttiMethod; 
    cls: TClass; 
begin 
    typ := ctx.GetType(TypeInfo(T)); 
    for mtd in typ.GetMethods do begin 
    if mtd.HasExtendedInfo and mtd.IsConstructor then 
    begin 
     if Length(mtd.GetParameters) = 0 then 
     begin 
     cls := typ.AsInstance.MetaclassType; 
     Result := mtd.Invoke(cls, []).AsType<T>; 
     exit; 
     end; 
    end; 
    end; 
    Result := nil; 
end; 

var 
    Dict: TDictionary<Integer, Integer>; 

begin 
    Dict := TInstance<TDictionary<Integer, Integer>>.Create; 
    Dict.Add(0, 0); 
    Writeln(BoolToStr(Dict.ContainsKey(0), True)); 
    Readln; 
end. 
+0

좋은 답변입니다! 하위 클래스 시도했지만 deserializer 발생 오류 : 내부 : Main.TDictionary 형식을 인스턴스화 할 수 없습니다. – aQuu

+0

해당 장치에서 RTTI를 비활성화 할 수 있습니까? 여기에서 말하기 힘듭니다. 분명히 내가 보여준 코드가 작동합니다. 그리고 저는 그 질문에 대답했다고 생각합니다. 우리가 볼 수 없기 때문에 특정 상황에 대해보다 자세한 조언을하는 것은 매우 어렵습니다. 적어도 당신은 무슨 일이 일어나고 있는지 이해합니다. –

+0

i TDictionary = class (TDictionary )를 직렬화하려고했지만 오류가 발생했습니다. 변경 declatation 후 : TDictionary = 클래스 (TDictionary ) 작동합니다! 나는 더 오래된 델파이 (XE2) mayby ​​thats 이유가 무엇입니까 – aQuu