원본 비디오(YouTube) 보기 (30 min)
DelphiCon 의 2021 시리즈 중, Things That You Don’t Know About JSON in Delphi - Dion Carlos Mai & Gustavo Mena Barreto (25 min) 의 한글 요약본입니다.
- 델파이에서 JSON을 다루는 방법, JSON 파서를 가장 생산적으로 사용하는 방법, JSON 직렬화 작동을 커스터마이징하고 자동화하는 옵션을 설명합니다.
- 발표자 Gustavo Mena Barreto는 Aquasoft 선임 개발자이며, Dion Carlos Mai은 Aquasoft 개발팀장입니다.
- 이 세션에서 사용된 전체 소스코드: https://github.com/gustavomenabarreto/delphicon2021
목차
델파이에서 JSON 다루기를 주제로 정한 이유
요즘 개발자에게 JSON은 매우 중요한 자리를 차지하고 있다.
- 요즘은 API 통합이 너무나 많다.
- 심지어 NoSQL JSON 데이터베이스도 있다.
JSON을 다루는 방법이 여러가지 라는 사실을 잘 모르는 개발자들이 있다.
- 이 세션에서는 JSON을 다루는 여러 가지 방법을 설명한다.
JSON을 다룰 때 델파이에서 선택할 수 있는 것들
- 가장 간단한 방법
- JsonToDelphi: JSON 오브젝트를 가지고 델파이 클래스를 자동 생성하는 웹사이트
- Super Object (오픈소스): 델파이 6, 7 등 구버전에서 JSON을 다룰 수 있음 (최신 버전 사용자에게는 권장하지 않음)
- RESTResponseDataSetAdapter 컴포넌트: JSON을 기반으로 데이터셋을 자동 생성
- TJSONMarshal 클래스: JSON 직렬화 처리를 커스터마이징할 때 사용
이 세션에서 사용할 JSON 샘플
그림. 이 세션의 코드는 비트코인 블록체인에서 사용되는 매우 구조가 큰 JSON에서 극히 일부분만 뽑아 놓은 샘플 JSON을 사용한다.
JSON을 사용하기 위해 필요한 델파이 유닛(들)
uses System.JSON, REST.Json;
1. 가장 간단한 방법
TJSONObject와 TJSONArray를 사용하는 방법
JSON 읽기와 쓰기 (아래 코드와 주석 참조)
// [JSON 읽기] var objeto : TJSONObject; ObjetoArray: TJSONArray; I: Integer; begin // ParseJSONValue를 이용하면, 단 한줄로 JSONObject를 만들 수 있다. objeto := TJsonObject.ParseJSONValue(MemoJson.Lines.Text) as TJSONObject; // JSONObject의 Get과 GetValue 메소드를 이용하면, JSON 개체의 모든 데이터에 접근할 수 있다. ShowMessage(objeto.Get('block_hash').ToString); ShowMessage(objeto.GetValue('block_hash').Value); // 배열 형식의 JSON값은 TJSONArray로 받을 수 있다. ObjetoArray := objeto.Get('inputs').JsonValue as TJSONArray; // JSONArray 안에 있는 각 요소는 for 루프를 통해 접근할 수 있다. for I := 0 to ObjetoArray.Size - 1 do begin MemoResultado.Lines.Add( ObjetoArray.Get(i).ToString + ': ' + ObjetoArray.Get(i).Value); end; end; // [JSON 쓰기] var objeto : TJSONObject; ObjetoArray: TJSONArray; ObjetoInputs: TJSONObject; begin objeto := TJSONObject.Create; ObjetoArray := TJSONArray.Create; // JSONObject의 AddPair (필드 이름, 필드 값) 메소드를 사용하여 데이터 쓰기 // 필드 값에 문자열 넣기 objeto.AddPair('block_hash','123456789'); // 필드 값에 JSONObject의 일종인 JSONArray 넣기 Objeto.AddPair('Inputs',ObjetoArray); // 'Input' 필드 값인 ObjetoArray에 들어 갈 JSONObject 만들기 ObjetoInputs := TJSONObject.Create; // 위와 동일하게 AddPair를 사용하여 JSONObject에 데이터 쓰기 ObjetoInputs.AddPair('prev_hash','987654321'); ObjetoInputs.AddPair('script','1'); // JSON 배열인 ObjetoArray의 Add 메소드를 이용하여, 배열 안에 요소 넣기 ObjetoArray.Add(ObjetoInputs); MemoResultado.Lines.Text := objeto.ToString; end;
2. JsonToDelphi
https://jsontodelphi.com는 JSON 샘플을 넣으면, 해당 JSON을 다룰 수 있는 델파이 클래스를 자동으로 받을 수 있는 웹사이트이다.
- SOAP / XML 파서와 사용 방식이 상당히 유사하지만, XML이 아니라 JSON을 다룬다.
-
우리가 직접 해당 클래스를 만들기 위해 수작업으로 코드를 작성할 수도 있지만,
- 많은 API들이 방대한 JSON을 사용하고 있는데,
- 그 JSON을 모두 클래스로 만들 기 위해 수작업으로 코드를 쓰려면,
- 시간도 많이 걸리고 너무 수고스럽다.
-
무료 서비스인 이 웹사이트를 이용하면
- 그 모든 수고를 덜 수 있다.
- 생성된 클래스의 전체 소스 코드를 다운로드 할 수 있으므로, 필요한 부분을 직접 변경할 수도 있다.
// [JsonToDelphi에서 만들어 준 클래스 사용하여 읽기] // 제공한 JSON 샘플의 구조가 모두 반영된 클래스가 생성되므로, 그냥 사용하면 된다. var TesteJson: TRoot; Inputs: TInputs; begin // JsonToDelphi가 만들어준 클래스 안에 JSON을 읽어서 넣기 TesteJson := TJson.JsonToObject<TRoot>(MemoJson.Lines.Text); // 데이터 타입까지 이미 알맞게 지정된 해당 클래스의 프로퍼티를 통해 JSON 필드를 접근 ShowMessage('BlockHash - '+TesteJson.BlockHash); for Inputs in TesteJson.Inputs do begin Showmessage('ScripType - '+Inputs.ScriptType); end; end;
// [JsonToDelphi에서 만들어 준 클래스 사용하여 쓰기] var TesteJson: TRoot; Inputs: TInputs; I: Integer; begin // JsonToDelphi에서 만들어 준 클래스를 생성하고 TesteJson := TRoot.Create; // 프로퍼티를 이용하여 값을 쓴다. TesteJson.BlockHash := 'www.aquasoft.com.br'; // 마찬가지 방법으로 배열을 돌면서 값을 쓸 수도 있다. for I := 0 to 2 do begin Inputs := TInputs.Create; Inputs.Age := 20; Inputs.Script := 'teste script'; TesteJson.Inputs.Add(Inputs); end; // 단 한줄로 JSON 데이터를 문자로 풀어낼 수 있다. MemoResultado.Lines.Text := //이 때 joIgnoreEmptyStrings 옵션을 사용하면, 값이 빈 문자열인 데이터는, JSON 필드를 만들 지 않고 건너뛴다. // joIgnoreEmptyStrings는 델파이의 TJsonOption의 일종이다 (아래 본문의 설명 참조) TJson.ObjectToJsonString(TesteJson,[joIgnoreEmptyStrings]); end;
JSON 직렬화 행위를 변경하는 옵션(들)
델파이의 REST.Json.TJsonOption
- https://docwiki.embarcadero.com/Libraries/Sydney/en/REST.Json.TJsonOption
-
JSON 생성 작동 방식 중 원하는 옵션을 선택할 수 있다.
- 예: 유닉스 날짜 포맷 사용하기, UTC 날짜 사용, 들여쓰기, 카멜 표기, 등등
-
델파이 버전이 최신일수록 선택할 수 있는 직렬화 옵션이 더 많다.
- 이전 버전이라서 필요한 옵션이 없다면, 원하는 작동을 하는 코드를 수작업으로 써 넣는다.
[그림. 델파이 11.0 알렉산드리아의 TJsonOption (참고: 이전 버전에는 옵션이 이것처럼 많지 않다)]
3. Super Object (오픈소스)
델파이 6, 7 등 구버전에서 JSON을 다룰 수 있음
- 무료 오픈 소스: https://github.com/hgourvest/superobject
-
델파이가 오래된 버전이지만, API 연동을 위해 JSON을 다루어야 한다면
- 이런 역할을 하는 DLL 또는 패키지의 도움을 받을 수 있다.
- 하지만, 직접 코드로 구현하려면 Super Object를 사용할 수 있다.
- 주의! 앞에서 설명했듯이, 최신 버전에는 이미 이런 기능들이 들어있으므로, Super Object를 굳이 사용할 이유가 없음
// [SuperObject를 사용하여 읽기] var Objeto: ISuperObject; Inputs: ISuperObject; begin // SuperObject의 SO 메소드를 사용하여 오브젝트를 생성 Objeto := SO(MemoJson.Lines.Text); // S 메소드는 원하는 필드와 값을 마치 배열처럼 접근 ShowMessage(Objeto.S['block_hash']); ShowMessage(Objeto['block_hash'].AsString); // for 루프 사용 가능 (for in 루프는 델파이 7 같은 구버전에는 없지만, for 문으로 유사하게 구현하면 된다.) for Inputs in objeto['inputs'] do begin MemoResultado.Lines.Add(Inputs.AsString); end; end; // [델파이 구버전에서 SuperObject를 사용하여 쓰기] var Objeto: ISuperObject; Inputs: ISuperObject; begin // 오브젝트 생성 Objeto := SO; // 데이터 쓰기 역시 마치 배열을 다루듯이 사용 Objeto.S['block_hash'] := '123456789'; Objeto.O['inputs'] := SA([]); Inputs := SO; Inputs.S['script'] := 'Teste script 1'; Inputs.I['age'] := 30; // JSON 배열을 쓸 수도 있다. Objeto.A['inputs'].O[0] := Inputs; Inputs := SO; Inputs.S['script'] := 'Teste script 2'; Inputs.I['age'] := 60; Objeto.A['inputs'].O[1] := Inputs; // AsString 메소드를 사용하여 JSON으로 쓸 수도 있다. MemoResultado.Lines.Text := Objeto.AsString; end;
4. RESTResponseDataSetAdapter 컴포넌트
RESTResponse에 받은 JSON 데이터를 FDMemTable 등의 데이터셋에 넣어주는 컴포넌트 (델파이 최근 버전에서 추가됨)
[그림. REST 디버거를 사용하면 더 쉽게 만들 수 있다]
REST API를 통해서 JSON 데이터를 받아서 델파이 폼에 표현하기
- REST 디버거 (REST API의 읽기와 쓰기를 테스트하는 무료 도구)를 실행한다.
-
테스트가 성공했다면, [Copy Components] 버튼을 클릭한다.
-
그러면, 해당 REST API를 다루도록 설정이 완료된 델파이 컴포넌트가 클립보드에 복사된다.
- RESTClient: REST 서비스 제공자에게 요청을하고 응답을 받는 사용자 오브젝트
- RESTRequest: RESTClient가 REST 서비스 제공자에게 전달하는 요청
- RESTResponse: RESTClient가 REST 서비스 제공자로부터 전달받은 응답
- RESTResponseDataSetAdapter: 전달받은 응답을 FDMemTable 등의 데이터셋에 넣어주는 매개체
- FDMemTable: 데이터셋
-
그러면, 해당 REST API를 다루도록 설정이 완료된 델파이 컴포넌트가 클립보드에 복사된다.
- 델파이 폼에서 붙여넣기를 하면, 복사된 컴포넌트가 모두 추가되고, 되델파이 프로젝트에서 바로 사용할 수 있다.
-
그 후에는, 일반 데이터와 마찬가지로 FDMemTable을 사용하면 된다.
- 함께 복사된 REST 관련 컴포넌트들이 자동으로 이 데이터셋에 JSON 데이터를 담는다.
- FDMemTable 이 가장 좋고 편한 데이터셋이기 때문에 사용한다 (원한다면 다른 데이터셋을 사용해도 된다)
- 예를 들어, 데이터가 표현될 그리드의 데이터소스 즉 DataSource 컴포넌트의 DataSet 프로퍼티에 FDMemTable를 지정하면 된다.
- 직접 작성해야할 코드는 2줄 뿐이다 (아래 코드 참조)
procedure TForm1.Button5Click(Sender: TObject); begin // REST 요청을 한다 RESTRequest1.Execute; // RESTResponse에 받은 JSON 데이터를 FDMemTable 등의 데이터셋에 넣기 RESTResponseDataSetAdapter1.Active := True; end;
5. TJSONMarshal 클래스
JSON 데이터 직렬화 알고리즘을 커스터마이징 할 수 있다.
-
RTTI를 직접 건드릴 필요가 없기 때문에 보다 안전하고 편하다.
- RTTI를 잘못 다루면 쓰레드 문제가 발생할 수 있다.
- JSON을 다룰 때는 쓰레드를 사용하고 JSON 구조를 알맞게 맞추어 쓰는 것이 좋다.
- 보다 자세한 내용은 사용자 오브젝트 직렬화에 대한 상세한 Docwiki 도움말을 참고하기 바란다.
// [TJSONMarshal을 사용하여 맞춤 JSON 구현하기] var lMarshal : TJSONMarshal; TesteJson: TRoot; //JsonToDelphi에서 제공하는 SuperObject 타입을 사용 Inputs: TInputs; I: Integer; begin TesteJson := TRoot.Create; // 오브젝트 생성 (SuperObject) TesteJson.BlockHash := 'www.aquasoft.com.br'; for I := 0 to 2 do begin Inputs := TInputs.Create; Inputs.Age := 20; Inputs.Script := 'teste script'; TesteJson.Inputs.Add(Inputs); end; // JSONMarshal을 생성할 때 컨버터를 지정 (커스터마이징을 위해 컨버터를 이용하는 것은 뒤에서 설명) lMarshal := TJSONMarshal.Create (TJSONConverter.Create); // Marshal 메소드를 사용하면 컨버터에 지정된 대로 컨버전 된다. MemoResultado.Lines.Text := lMarshal.Marshal(TesteJson).ToString(); lMarshal.Free; end;
JSON 커스터마이징 방식 1: JSONMarshalled 어트리뷰트 사용
[JSONMarshalled(False)] // 이 어트리뷰트에서 False로 지정된 것, 즉 FScript는 생성될 JSON의 필드에 들어가지 않는다. FScript: string;
JSON 커스터마이징 방식 2: RegisterConverter 프로시저를 오버로딩하기
- 프로시저 오버로딩을 통해 파서인 TJSONMarshal 클래스의 기본 전환 방식을 덮어쓰는 방법
- JSON의 특정 필드의 데이터 타입 바꾸기 등 얼마든지 처리 로직을 바꿀 수 있다. (굳이 RTTI를 건드리지 않아도 된다)
- 좀 복잡하긴 하지만, 필요하다면 이처럼 TJSONMarshal 클래스를 생성할 때 컨버터를 파라미터로 넣는 방식을 쓸 수 있다.
Recommended Comments
There are no comments to display.