[C#] JSON -> DataTable 변환시 DBNull 문제

2014. 3. 5. 20:08Coders

Newtonsoft.Json.dll 라는 아주 훌륭한 JSON 라이브러리가 있습니다.

다른 라이브러리에 비해 쉽게 데이터를 파싱할 수 가 있는데요, 어쨌거나 저도 이 라이브러리를 사용하여 뭔가 개발에 참여하고 있습니다. 그런데, 문제가 좀 발생합니다. 첨부 이미지 보시죠.


자체적으로 DataGridView 를 이용하여 만든 데이터 뷰어 입니다.



분홍색 부분을 보시면, 사실상 저 데이터의 데이터타입은 boolean(SQL서버에서는 bit 타입)입니다만, 하필, bit 이면서 null(C#에서는 DBNull.Value) 입니다. 좀 우기는 것 같긴 하지만, 양 옆의 데이터들도 체크되지 않은 값 들은 null 인 값이 좀 있습니다.


그런데, 문제는 여기에서 발생합니다. 서버에서 조회 한 데이터를 넘기면, 클라이언트에서는 앞서 언급한 라이브러리의 스태틱 메서드인 Newtonsoft.Json.JsonConvert.PopulateObject 메서드를 가지고 JSON 데이터를 데이터 테이블, 또는 데이터셋으로 바꿔 주는 과정을 거치는 데, 하필, DataType 이 Boolean 이면서, 맨 첫번째 로우의 값이 null 이면, 자체적으로 헷갈리는 지, 데이터 타입을 String 으로 인지합니다.


제가 만든 데이터 뷰어를 보시면, 왼쪽엔 스키마를, 오른쪽엔 데이터를 뿌려주도록 만들었는데요, 왼쪽의 녹색 부분은 실제 데이터베이스에서는 bit 즉, boolean 값이거든요. 그런데, 그 중에서도 빨갛게 두른 부분을 보시면, String 이라고 나옵니다. 추측하건데, JsonConvert 의 메서드가 첫번째 로우의 데이터가 null 이면 혼란을 일으켜 그냥 가장 무난한 String 데이터 타입으로 데이터테이블을 생성하는 것으로 보입니다.

(※ 제가 예전 버전이라서 그런 것일 수도 있습니다만)


아무튼, 그래서, 저 기능이 데이터를 많이 조회하는 것도 아니고 해서, 특정 컬럼명에 대해(그게 boolean값이라고 알고 있다 가정하고) 데이터 타입이 boolean 이 아니면 boolean 값으로 바꿔주는 메서드를 하나 작성했습니다. 원리는 간단합니다. 데이터가 있는 컬럼의 데이터 타입은 바꾸지 못하기 때문에, 임시 컬럼(boolean)을 하나 추가 하고, 맞는 데이터를 넣은 후, 기존 컬럼은 제거, 임시 컬럼의 이름을 기존 컬럼의 이름으로 바꾸는 겁니다. 이거 좀 삽질 같기는 하지만 처음엔 바인딩 되는 그리드가 문제인가 싶어 한참을 그리드 쪽만 쳐다 보다가 겨우 알아내어 그냥 좀 적어 놓습니다.


어찌 생각해 보면, Newtonsoft.Json 라이브러리 뿐만 아니라, "데이터가 있는 테이블의 컬럼 타입 바꾸기" 정도가 되는 것 같네요.


소스입니다. 조금 깁니다.


/// <summary>
/// DataTable에서 Boolean 컬럼인데, 첫번째 로우의 값이 null 이어서 String으로 바뀐 값을 보정
/// </summary>
/// <param name="dt">데이터테이블</param>
/// <param name="boolColumnList">컬럼리스트</param>
public static void ConvertColumnToBoolean(DataTable dt, string[] boolColumnList)
{
	try
	{
		if (dt == null || dt.Rows.Count < 1 || boolColumnList == null || boolColumnList.Length < 1)
		{
			return;
		}

		dt.BeginLoadData();

		Dictionary<string, string> tempCols = new Dictionary<string, string>();

		foreach (string col in boolColumnList)
		{
			if (dt.Columns.Contains(col) && dt.Columns[col].DataType != typeof(bool))
			{
				string tempCol = Guid.NewGuid().ToString();
				DataColumn oldCol = dt.Columns[col];
				DataColumn newCol = dt.Columns.Add(tempCol, typeof(bool));
				newCol.ColumnMapping = oldCol.ColumnMapping;
				newCol.Caption = oldCol.Caption;

				tempCols.Add(col, tempCol);
			}
		}

		if (tempCols.Count < 1)
		{
			//변환할 게 없음.
			return;
		}

		foreach (DataRow row in dt.Rows)
		{
			foreach (KeyValuePair<string, string> tempCol in tempCols)
			{
				if (row[tempCol.Key] == null || row[tempCol.Key] == DBNull.Value)
				{
					continue;
				}
				else
				{
					string strData = row[tempCol.Key].ToString().Trim();

					if (bool.TrueString.Equals(strData, StringComparison.InvariantCultureIgnoreCase))
					{
						row[tempCol.Value] = true;
					}
					else if (bool.FalseString.Equals(strData, StringComparison.InvariantCultureIgnoreCase))
					{
						row[tempCol.Value] = false;
					}
				}
			}
		}

		foreach (KeyValuePair<string, string> tempCol in tempCols)
		{
			dt.Columns.Remove(tempCol.Key);
			dt.Columns[tempCol.Value].ColumnName = tempCol.Key;
		}

		dt.EndLoadData();
		dt.AcceptChanges();
	}
	catch (System.Exception ex)
	{
		throw ex;
	}
}