JSON은 그 형식의 단순함과 유연성 덕분에 요새는 정말 많은곳에서 사용하고 있습니다. 특히 웹 개발에선 표준(?)이 아닐까 싶을 정도로 JSON을 자주 사용합니다. 이러한 추세에 맞춰 C#에서는 최신 .NET에서 JSON 관련 기능을 강화해왔으며, 특히 JSON을 직렬화 및 역직렬화하는 데 있어 Class를 정의하는 것을 권장하고 있습니다.
C#을 사용하여 JSON 직렬화 및 역직렬화 - .NET
그러나 현업에서 수많은 클라이언트-서버 간 데이터를 처리하면서 JSON 특유의 유연함과 다양한 구조에 의해 이러한 방식이 효율적인가?하는 의문이 들었습니다. 이 글은 이런 저의 생각을 공유하고 피드백을 받고 싶어 작성하게 되었습니다.
0. 예제
설명을 위해 현업에서 사용하고 있는 데이터를 약간 가공해 보았습니다. 너무 복잡하지도, 단순하지도 않은 구조를 가진 JSON 입니다.
{
"ScreeningDataInfo": {
"ShopName": "닷넷데브",
"FileName": "닷넷데브 2024-08-01 심사자료",
"EDealerRequestDate": "",
"Category": "",
"StartDate": "2023-08-01",
"EndDate": "2024-07-31"
},
"ScreeningDataList":
[
{
"ShopID": "닷넷데브",
"MallID": "닷넷데브sub",
"MallTypeCode": "32",
"VendorCode": "KB",
"SalesReturnData": [
{
"Year": "2023",
"Month": "07",
"SalesAmount": 2185820.0,
"ReturnAmount": 88400.0,
"ReturnRate": 4.04
},
{
"Year": "2023",
"Month": "08",
"SalesAmount": 708540.0,
"ReturnAmount": 0.0,
"ReturnRate": 0.0
},
{
"Year": "2023",
"Month": "09",
"SalesAmount": 1212910.0,
"ReturnAmount": 0.0,
"ReturnRate": 0.0
},
{
"Year": "2023",
"Month": "10",
"SalesAmount": 604500.0,
"ReturnAmount": 19840.0,
"ReturnRate": 3.28
},
{
"Year": "2023",
"Month": "11",
"SalesAmount": 782350.0,
"ReturnAmount": 0.0,
"ReturnRate": 0.0
},
{
"Year": "2023",
"Month": "12",
"SalesAmount": 674390.0,
"ReturnAmount": 0.0,
"ReturnRate": 0.0
},
{
"Year": "2024",
"Month": "01",
"SalesAmount": 1038410.0,
"ReturnAmount": 28930.0,
"ReturnRate": 2.78
},
{
"Year": "2024",
"Month": "02",
"SalesAmount": 344400.0,
"ReturnAmount": 95100.0,
"ReturnRate": 27.61
},
{
"Year": "2024",
"Month": "03",
"SalesAmount": 145620.0,
"ReturnAmount": 0.0,
"ReturnRate": 0.0
},
{
"Year": "2024",
"Month": "04",
"SalesAmount": 550370.0,
"ReturnAmount": 59860.0,
"ReturnRate": 10.87
},
{
"Year": "2024",
"Month": "05",
"SalesAmount": 224300.0,
"ReturnAmount": 27800.0,
"ReturnRate": 12.39
},
{
"Year": "2024",
"Month": "06",
"SalesAmount": 0.0,
"ReturnAmount": 0.0,
"ReturnRate": 0.0
},
{
"Year": "2024",
"Month": "07",
"SalesAmount": 0.0,
"ReturnAmount": 0.0,
"ReturnRate": 0.0
},
{
"Year": "2024",
"Month": "08",
"SalesAmount": 499370.0,
"ReturnAmount": 0.0,
"ReturnRate": 0.0
}
],
"FirstExpectPayAmount": 8651050.0,
"MallTypeNameKor": "닷넷데브",
"MallType": 32,
"Total" : {
"SalesAmount": 8970980.0,
"ReturnAmount": 319930.0,
"ReturnRate": 3.56
}
}
]
}
1. Class를 생성하는 것의 비효율성
주어진 JSON 데이터 구조를 반영하기 위해서는 여러 개의 클래스가 필요합니다. 왜냐하면 각 중첩된 객체와 배열에 대해 별도의 클래스를 만들어야 하기 때문입니다.
예를 들어, 다음과 같은 클래스들이 필요할 것입니다.
public class ScreeningDataInfo
{
public string ShopName { get; set; }
public string FileName { get; set; }
public string EDealerRequestDate { get; set; }
public string Category { get; set; }
public string StartDate { get; set; }
public string EndDate { get; set; }
}
public class SalesReturnData
{
public string Year { get; set; }
public string Month { get; set; }
public double SalesAmount { get; set; }
public double ReturnAmount { get; set; }
public double ReturnRate { get; set; }
}
public class ScreeningDataList
{
public string ShopID { get; set; }
public string MallID { get; set; }
public string MallTypeCode { get; set; }
public string VendorCode { get; set; }
public SalesReturnData[] SalesReturnData { get; set; }
}
public class Total
{
public double SalesAmount { get; set; }
public double ReturnAmount { get; set; }
public double ReturnRate { get; set; }
}
public class Data
{
public ScreeningDataInfo ScreeningDataInfo { get; set; }
public ScreeningDataList[] ScreeningDataList { get; set; }
public double FirstExpectPayAmount { get; set; }
public string MallTypeNameKor { get; set; }
public int MallType { get; set; }
public Total Total { get; set; }
}
이 클래스들을 볼 때 제가 생각하는 문제는 크게 3가지입니다.
- 클래스의 중복 생성: JSON 구조가 복잡할수록 많은 클래스가 필요합니다. 이러한 클래스들은 특정 JSON 형식에 의존하기 때문에 재사용성이 낮고, 유사한 구조에 대해서도 새로운 클래스를 작성해야 할 수 있습니다.
- 유지보수 어려움: JSON 구조가 변경될 때마다 관련된 클래스들을 수정해야 하며, 이는 유지보수의 부담을 가중시킵니다. 예를 들어, JSON에 새로운 필드가 추가되면 해당 필드를 포함하도록 클래스를 수정해야 합니다.
- 비효율적인 메모리 사용: 클래스 생성과 관리에는 메모리와 성능 비용이 수반됩니다. 특히, 필요하지 않은 클래스까지 생성하게 되는 경우 불필요한 리소스 낭비로 이어질 수 있습니다.
2. Newtonsoft.Json을 활용한 직렬화/역직렬화의 장점
반면, Newtonsoft.Json 라이브러리를 사용하면 클래스 정의 없이도 JSON 데이터를 다룰 수 있으며, 매우 간편하게 JSON을 처리할 수 있습니다.
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
var json = @"
{
'ScreeningDataInfo': {
'ShopName': '닷넷데브',
'FileName': '닷넷데브 2024-08-01 심사자료',
'EDealerRequestDate': '',
'Category': '',
'StartDate': '2023-08-01',
'EndDate': '2024-07-31'
},
'ScreeningDataList': [
{
'ShopID': '닷넷데브',
'MallID': '닷넷데브sub',
'MallTypeCode': '32',
'VendorCode': 'KB',
'SalesReturnData': [
{
'Year': '2023',
'Month': '07',
'SalesAmount': 2185820.0,
'ReturnAmount': 88400.0,
'ReturnRate': 4.04
},
...
]
}
],
'FirstExpectPayAmount': 8651050.0,
'MallTypeNameKor': '닷넷데브',
'MallType': 32,
'Total': {
'SalesAmount': 8970980.0,
'ReturnAmount': 319930.0,
'ReturnRate': 3.56
}
}";
// JSON을 dynamic 객체로 역직렬화
dynamic data = JsonConvert.DeserializeObject(json);
// 원하는 데이터에 바로 접근 가능
string shopName = data.ScreeningDataInfo.ShopName;
double totalSales = data.Total.SalesAmount;
제가 생각하는 Newtonsoft.Json의 장점은 아래와 같습니다.
- 유연한 데이터 접근:
dynamic
이나JObject
를 사용하면, JSON 구조에 맞춰 클래스 없이도 데이터에 직접 접근할 수 있습니다. JSON 구조가 변경되더라도 코드를 크게 수정할 필요가 없습니다. - 간단한 구현: 클래스 정의 없이 JSON 데이터를 쉽게 직렬화/역직렬화할 수 있습니다. 이로 인해 코드가 간결해지고 유지보수가 용이해집니다.
3. 직렬화시 var (암시적 형식 지역 변수) 사용
C#에서 var
키워드는 컴파일러가 변수의 타입을 자동으로 추론할 수 있도록 도와줍니다. var
를 사용하여 익명 객체를 정의하고, Newtonsoft.Json을 사용해 JSON 문자열로 직렬화하는 예를 살펴보겠습니다.
var json = new
{
ScreeningDataInfo = new
{
ShopName = "닷넷데브",
FileName = "닷넷데브 2024-08-01 심사자료",
EDealerRequestDate = "",
Category = "",
StartDate = "2023-08-01",
EndDate = "2024-07-31"
},
ScreeningDataList = new[]
{
new
{
ShopID = "닷넷데브",
MallID = "닷넷데브sub",
MallTypeCode = "32",
VendorCode = "KB",
SalesReturnData = new []
{
new
{
Year = "2023",
Month = "07",
SalesAmount = 2185820.0,
ReturnAmount = 88400.0,
ReturnRate = 4.04
},
new
{
Year = "2023",
Month = "08",
SalesAmount = 708540.0,
ReturnAmount = 0.0,
ReturnRate = 0.0
},
...
}
}
},
FirstExpectPayAmount = 8651050.0,
MallTypeNameKor = "닷넷데브",
MallType = 32,
Total = new
{
SalesAmount = 8970980.0,
ReturnAmount = 319930.0,
ReturnRate = 3.56
}
};
// JSON으로 직렬화
string jsonString = JsonConvert.SerializeObject(jsonObject);
Console.WriteLine(jsonString);
이 방식은 크게 4개의 장점이 있습니다.
- 코드 간결화:
var
를 사용하면 변수 타입을 명시할 필요가 없어 코드가 간결해지고, 가독성이 높아집니다. 특히, 복잡한 타입을 가진 익명 객체를 다룰 때 유용합니다. - 타입 추론: 컴파일러가 타입을 자동으로 추론해 주기 때문에, 변수를 선언할 때 일일이 타입을 지정하지 않아도 됩니다.
- 유연성:
var
를 사용하면 JSON 직렬화 시 익명 타입을 생성하고, 쉽게 JSON 문자열로 변환할 수 있습니다. - 직관성:
var
를 사용하여 익명 객체를 생성할 때, 객체의 구조는 JSON 형식과 거의 동일하게 표현됩니다. 이는 코드가 어떻게 JSON으로 직렬화될지 쉽게 예측할 수 있게 해줍니다.
특히 유연성과 직관성을 제공한다는 점에서 저는 이방식을 선호합니다.
4. 역직렬화시 DataTable 사용
JSON 데이터를 역직렬화하여 DataTable
로 변환하는 방법은, 특히 테이블 형태의 데이터를 처리할 때 매우 유용합니다. 이 방법은 데이터베이스와의 상호작용 또는 다양한 데이터 조작을 손쉽게 수행할 수 있게 해줍니다.
string jsonString = @"
{
'ScreeningDataList': [
{
'ShopID': '닷넷데브',
'MallID': '닷넷데브sub',
'MallTypeCode': '32',
'VendorCode': 'KB',
'SalesReturnData': [
{
'Year': '2023',
'Month': '07',
'SalesAmount': 2185820.0,
'ReturnAmount': 88400.0,
'ReturnRate': 4.04
},
{
'Year': '2023',
'Month': '08',
'SalesAmount': 708540.0,
'ReturnAmount': 0.0,
'ReturnRate': 0.0
}
]
}
]
}";
// JSON을 DataTable로 변환
JObject jsonObject = JObject.Parse(jsonString);
JArray salesReturnDataArray = (JArray)jsonObject["ScreeningDataList"][0]["SalesReturnData"];
DataTable dataTable = new DataTable();
dataTable.Columns.Add("Year", typeof(string));
dataTable.Columns.Add("Month", typeof(string));
dataTable.Columns.Add("SalesAmount", typeof(double));
dataTable.Columns.Add("ReturnAmount", typeof(double));
dataTable.Columns.Add("ReturnRate", typeof(double));
foreach (var item in salesReturnDataArray)
{
DataRow row = dataTable.NewRow();
row["Year"] = item["Year"];
row["Month"] = item["Month"];
row["SalesAmount"] = item["SalesAmount"];
row["ReturnAmount"] = item["ReturnAmount"];
row["ReturnRate"] = item["ReturnRate"];
dataTable.Rows.Add(row);
}
// DataTable 출력
foreach (DataRow row in dataTable.Rows)
{
Console.WriteLine($"{row["Year"]}, {row["Month"]}, {row["SalesAmount"]}, {row["ReturnAmount"]}, {row["ReturnRate"]}");
}
JSON 데이터를 DataTable
로 변환하면, 구조화된 형태로 데이터를 다룰 수 있습니다. 이는 특히 대량의 데이터나 복잡한 데이터 조작이 필요한 경우에 유리합니다.
결론
C#에서 JSON을 다루기 위해 Class를 정의하는 것은 명시적이고 타입 안전한 코드를 작성할 수 있다는 장점이 있을것입니다. 그러나 점점 더 다양하고 복잡해지는 JSON 구조에 대응하기 위해선 더욱더 다양한 접근 방식을 고려해볼 필요가 있다고 생각합니다.
그래서 이번에 제가 평소에 사용하는 몇 가지 JSON 처리 방식을 소개해 보았습니다. 이러한 방법들이 특정 상황에서 유용할 수 있지만, 혹시 제가 놓친 중요한 이슈라던가 여기서 다루지 않은 다른 좋은 JSON 처리 방법도 많을거라 생각합니다.
피드백을 주시면 열심히 배워보겠습니다.
감사합니다.