386 lines
16 KiB
C#
386 lines
16 KiB
C#
#nullable enable
|
|
using Newtonsoft.Json.Linq;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace UVC.Data
|
|
{
|
|
/// <summary>
|
|
/// JSON 데이터의 유효성을 검사하는 클래스입니다.
|
|
/// 이 클래스는 특정 속성(프로퍼티)에 대한 유효성 검사 규칙을 추가하고,
|
|
/// 데이터가 해당 규칙을 모두 충족하는지 확인할 수 있습니다.
|
|
/// </summary>
|
|
/// <example>
|
|
/// <code>
|
|
/// // 유효성 검사기 생성
|
|
/// var _validator = new DataValidator();
|
|
///
|
|
/// // 검증 규칙 추가: name 속성은 비어있지 않아야 함
|
|
/// _validator.AddValidator("name", value => value is string name && !string.IsNullOrEmpty(name));
|
|
///
|
|
/// // 검증 규칙 추가: age 속성은 18 이상이어야 함
|
|
/// _validator.AddValidator("age", value => value is long age && age >= 18);
|
|
///
|
|
/// // 데이터 검증 예시
|
|
/// var userData = new JObject {
|
|
/// { "name", "홍길동" },
|
|
/// { "age", 20 }
|
|
/// };
|
|
///
|
|
/// if (_validator.IsValid(userData))
|
|
/// {
|
|
/// Console.WriteLine("유효한 사용자 데이터입니다.");
|
|
/// }
|
|
/// else
|
|
/// {
|
|
/// Console.WriteLine("유효하지 않은 사용자 데이터입니다.");
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
public class DataValidator
|
|
{
|
|
/// <summary>
|
|
/// 유효성 검사 규칙의 목록입니다.
|
|
/// 각 사전은 속성 이름(string)과 해당 속성의 검증 함수(Func<object, bool>)를 포함합니다.
|
|
/// </summary>
|
|
private List<Dictionary<string, Func<object?, bool>>> validaters = new List<Dictionary<string, Func<object?, bool>>>();
|
|
|
|
/// <summary>
|
|
/// 대용량 JSON 데이터를 스트리밍 방식으로 처리할지 여부를 나타내는 속성입니다.
|
|
/// 스트리밍 방식은 메모리 사용량을 줄이기 위해 대용량 데이터를 처리할 때 유용합니다.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// true로 설정하면 대용량 JSON 데이터를 처리할 때 스트리밍 방식을 사용합니다.
|
|
/// false로 설정하면 항상 전체 데이터를 메모리에 로드합니다.
|
|
/// </remarks>
|
|
public bool SupportsStreamParsing { get; internal set; } = true;
|
|
|
|
/// <summary>
|
|
/// 스트리밍 방식을 적용할 JSON 데이터의 최소 길이(바이트)입니다.
|
|
/// 데이터 길이가 이 값보다 크면 스트리밍 방식으로 처리합니다.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 기본값은 10,000바이트입니다. 작은 데이터의 경우 스트리밍 처리가 오히려
|
|
/// 오버헤드를 발생시킬 수 있으므로, 특정 크기 이상일 때만 스트리밍 방식을 적용합니다.
|
|
/// </remarks>
|
|
public int SupportsStreamLength { get; internal set; } = 10000;
|
|
|
|
/// <summary>
|
|
/// 새로운 유효성 검사 규칙을 추가합니다.
|
|
/// </summary>
|
|
/// <param name="propertyName">검사할 속성 이름</param>
|
|
/// <param name="validator">유효성 검사 함수 (매개변수: 속성값, 반환값: 유효성 여부)</param>
|
|
/// <example>
|
|
/// <code>
|
|
/// var _validator = new DataValidator();
|
|
///
|
|
/// // 문자열 속성이 비어있지 않은지 확인하는 규칙
|
|
/// _validator.AddValidator("username", value =>
|
|
/// {
|
|
/// if (value is string str)
|
|
/// return !string.IsNullOrEmpty(str);
|
|
/// return false;
|
|
/// });
|
|
///
|
|
/// // 숫자 속성이 특정 범위 내에 있는지 확인하는 규칙
|
|
/// _validator.AddValidator("score", value =>
|
|
/// {
|
|
/// if (value is long num)
|
|
/// return num >= 0 && num <= 100;
|
|
/// return false;
|
|
/// });
|
|
/// </code>
|
|
/// </example>
|
|
public void AddValidator(string propertyName, Func<object?, bool> validator)
|
|
{
|
|
// 유효성 검사기를 추가합니다.
|
|
validaters.Add(new Dictionary<string, Func<object?, bool>> { { propertyName, validator } });
|
|
}
|
|
|
|
/// <summary>
|
|
/// 특정 속성에 대한 모든 유효성 검사 규칙을 제거합니다.
|
|
/// </summary>
|
|
/// <param name="propertyName">규칙을 제거할 속성 이름</param>
|
|
/// <example>
|
|
/// <code>
|
|
/// var _validator = new DataValidator();
|
|
/// _validator.AddValidator("age", value => (long)value >= 18);
|
|
///
|
|
/// // 검증 과정 중에 'age' 속성 검사가 더 이상 필요하지 않을 경우
|
|
/// _validator.RemoveValidator("age");
|
|
/// </code>
|
|
/// </example>
|
|
public void RemoveValidator(string propertyName)
|
|
{
|
|
// 유효성 검사기를 제거합니다.
|
|
validaters.RemoveAll(v => v.ContainsKey(propertyName));
|
|
}
|
|
|
|
/// <summary>
|
|
/// DataObject 객체가 모든 유효성 검사 규칙을 통과하는지 확인합니다.
|
|
/// </summary>
|
|
/// <param name="data">검사할 DataObject</param>
|
|
/// <returns>모든 규칙을 통과하면 true, 하나라도 실패하면 false</returns>
|
|
/// <example>
|
|
/// <code>
|
|
/// var _validator = new DataValidator();
|
|
/// _validator.AddValidator("email", value => {
|
|
/// var emailStr = value as string;
|
|
/// return emailStr != null && emailStr.Contains("@");
|
|
/// });
|
|
///
|
|
/// var userData = new DataObject();
|
|
/// userData["name"] = "홍길동";
|
|
/// userData["email"] = "hong@example.com";
|
|
///
|
|
/// bool isValid = _validator.IsValid(userData); // true 반환
|
|
/// </code>
|
|
/// </example>
|
|
public bool IsValid(DataObject? data)
|
|
{
|
|
// 데이터가 null인 경우 유효하지 않음
|
|
if (data == null)
|
|
{
|
|
return false;
|
|
}
|
|
// 각 유효성 검사기를 순회하며 검증을 수행합니다.
|
|
foreach (var validator in validaters)
|
|
{
|
|
foreach (var kvp in validator)
|
|
{
|
|
// 해당 속성이 데이터에 존재하는지 확인
|
|
if (!data.ContainsKey(kvp.Key))
|
|
{
|
|
continue; // 속성이 없으면 검증을 건너뜀
|
|
}
|
|
// 속성 값에 대한 검증을 수행
|
|
if (!kvp.Value(data[kvp.Key]))
|
|
{
|
|
// 검증에 실패하면 false를 반환
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 모든 검증을 통과하면 유효함
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// JObject가 모든 유효성 검사 규칙을 통과하는지 확인합니다.
|
|
/// </summary>
|
|
/// <param name="data">검사할 JObject</param>
|
|
/// <returns>모든 규칙을 통과하면 true, 하나라도 실패하면 false</returns>
|
|
/// <example>
|
|
/// <code>
|
|
/// var _validator = new DataValidator();
|
|
///
|
|
/// // 사용자 나이가 18세 이상인지 확인하는 규칙
|
|
/// _validator.AddValidator("age", value =>
|
|
/// value is long age && age >= 18
|
|
/// );
|
|
///
|
|
/// // JSON 데이터 생성
|
|
/// var jsonObject = JObject.Parse(@"{
|
|
/// ""name"": ""김철수"",
|
|
/// ""age"": 16
|
|
/// }");
|
|
///
|
|
/// bool isValid = _validator.IsValid(jsonObject); // false 반환 (age가 18 미만)
|
|
/// </code>
|
|
/// </example>
|
|
public bool IsValid(JObject? data)
|
|
{
|
|
// 데이터가 null인 경우 유효하지 않음
|
|
if (data == null)
|
|
{
|
|
return false;
|
|
}
|
|
// 각 유효성 검사기를 순회하며 검증을 수행합니다.
|
|
foreach (var validator in validaters)
|
|
{
|
|
foreach (var kvp in validator)
|
|
{
|
|
// 해당 속성이 데이터에 존재하는지 확인
|
|
if (!data.ContainsKey(kvp.Key))
|
|
{
|
|
continue; // 속성이 없으면 검증을 건너뜀
|
|
}
|
|
// 속성 값에 대한 검증을 수행
|
|
if (!kvp.Value(ConvertJTokenToObject(data[kvp.Key])))
|
|
{
|
|
// 검증에 실패하면 false를 반환
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 모든 검증을 통과하면 유효함
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// JSON 데이터 스트림이 모든 유효성 검사 규칙을 통과하는지 확인합니다.
|
|
/// 대용량 JSON 데이터를 메모리 효율적으로 처리할 수 있습니다.
|
|
/// </summary>
|
|
/// <param name="jsonStream">검사할 JSON 데이터 스트림</param>
|
|
/// <returns>모든 규칙을 통과하면 true, 하나라도 실패하면 false</returns>
|
|
/// <example>
|
|
/// <code>
|
|
/// var _validator = new DataValidator();
|
|
/// _validator.AddValidator("isActive", value => value is bool active && active == true);
|
|
///
|
|
/// // 파일에서 JSON 데이터 스트림을 읽어 유효성 검사
|
|
/// using (var fileStream = File.OpenRead("user-data.json"))
|
|
/// {
|
|
/// bool isValid = _validator.IsValid(fileStream);
|
|
/// Console.WriteLine($"데이터 유효성: {isValid}");
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
public bool IsValid(System.IO.Stream jsonStream)
|
|
{
|
|
if (jsonStream == null) return false;
|
|
|
|
// 스트림 처리 최적화를 위해 청크 단위로 읽을 수 있지만,
|
|
// 현재는 Newtonsoft.Json의 기본 역직렬화 사용
|
|
using (var reader = new Newtonsoft.Json.JsonTextReader(new System.IO.StreamReader(jsonStream)))
|
|
{
|
|
// 청크 읽기 설정 - 메모리 사용량 최적화
|
|
reader.SupportMultipleContent = true;
|
|
|
|
var serializer = new Newtonsoft.Json.JsonSerializer();
|
|
var sourceObject = serializer.Deserialize<JObject>(reader);
|
|
|
|
return IsValid(sourceObject);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// JSON 배열에서 유효성 검사 규칙을 통과하는 항목만 필터링하여 반환합니다.
|
|
/// </summary>
|
|
/// <param name="data">필터링할 JSON 배열</param>
|
|
/// <returns>유효한 항목만 포함된 새 JSON 배열 (유효한 항목이 없으면 null)</returns>
|
|
/// <example>
|
|
/// <code>
|
|
/// var _validator = new DataValidator();
|
|
///
|
|
/// // 유효성 검사 규칙: status가 'active'인 항목만 유효
|
|
/// _validator.AddValidator("status", value =>
|
|
/// value is string status && status == "active"
|
|
/// );
|
|
///
|
|
/// // 필터링할 JSON 배열 생성
|
|
/// var usersArray = JArray.Parse(@"[
|
|
/// {""id"": 1, ""name"": ""홍길동"", ""status"": ""active""},
|
|
/// {""id"": 2, ""name"": ""김철수"", ""status"": ""inactive""},
|
|
/// {""id"": 3, ""name"": ""이영희"", ""status"": ""active""}
|
|
/// ]");
|
|
///
|
|
/// // 유효한 항목(status가 'active'인 항목)만 필터링
|
|
/// JArray? validUsers = _validator.GetValidData(usersArray);
|
|
///
|
|
/// // validUsers는 홍길동과 이영희의 데이터만 포함 (2개 항목)
|
|
/// Console.WriteLine($"유효한 사용자 수: {validUsers?.Count ?? 0}");
|
|
/// </code>
|
|
/// </example>
|
|
public JArray? GetValidData(JArray? data)
|
|
{
|
|
if (data == null || data.Count == 0)
|
|
{
|
|
return data; // 빈 배열은 유효하지 않음
|
|
}
|
|
|
|
JArray validData = new JArray();
|
|
foreach (var item in data)
|
|
{
|
|
if (item is JObject jObject && IsValid(jObject))
|
|
{
|
|
validData.Add(jObject);
|
|
}
|
|
}
|
|
return validData.Count > 0 ? validData : null; // 유효한 데이터가 없으면 null 반환
|
|
}
|
|
|
|
/// <summary>
|
|
/// JSON 데이터 스트림에서 유효성 검사 규칙을 통과하는 항목만 필터링하여 반환합니다.
|
|
/// 대용량 JSON 배열을 메모리 효율적으로 처리하기 위해 스트리밍 방식을 사용합니다.
|
|
/// </summary>
|
|
/// <param name="jsonStream">필터링할 JSON 배열 데이터 스트림</param>
|
|
/// <returns>유효한 항목만 포함된 JSON 배열 (유효한 항목이 없으면 null)</returns>
|
|
/// <example>
|
|
/// <code>
|
|
/// var _validator = new DataValidator();
|
|
///
|
|
/// // 필수 필드가 모두 있는지 확인하는 규칙 추가
|
|
/// _validator.AddValidator("name", value => value != null && !string.IsNullOrEmpty(value.ToString()));
|
|
/// _validator.AddValidator("email", value => value != null && value.ToString().Contains("@"));
|
|
///
|
|
/// // 파일에서 JSON 배열 데이터 스트림을 읽어 유효한 항목만 필터링
|
|
/// using (var fileStream = File.OpenRead("users-data.json"))
|
|
/// {
|
|
/// JArray? validUsers = _validator.GetValidData(fileStream);
|
|
///
|
|
/// if (validUsers != null)
|
|
/// {
|
|
/// Console.WriteLine($"유효한 사용자 수: {validUsers.Count}");
|
|
///
|
|
/// // 첫 번째 유효한 사용자 정보 출력
|
|
/// if (validUsers.Count > 0)
|
|
/// {
|
|
/// var firstUser = validUsers[0];
|
|
/// Console.WriteLine($"첫 번째 유효 사용자: {firstUser["name"]}");
|
|
/// }
|
|
/// }
|
|
/// else
|
|
/// {
|
|
/// Console.WriteLine("유효한 사용자가 없습니다.");
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
public JArray? GetValidData(System.IO.Stream jsonStream)
|
|
{
|
|
if (jsonStream == null) return null;
|
|
|
|
using (var reader = new Newtonsoft.Json.JsonTextReader(new System.IO.StreamReader(jsonStream)))
|
|
{
|
|
// 청크 읽기 설정 - 메모리 사용량 최적화
|
|
reader.SupportMultipleContent = true;
|
|
|
|
var serializer = new Newtonsoft.Json.JsonSerializer();
|
|
var sourceArray = serializer.Deserialize<JArray>(reader);
|
|
|
|
return GetValidData(sourceArray);
|
|
}
|
|
}
|
|
|
|
private object? ConvertJTokenToObject(JToken? token)
|
|
{
|
|
if(token == null || token.Type == JTokenType.Null)
|
|
{
|
|
return null; // null 처리
|
|
}
|
|
switch (token.Type)
|
|
{
|
|
case JTokenType.String:
|
|
return token.ToString();
|
|
case JTokenType.Integer:
|
|
return token.ToObject<int>();
|
|
case JTokenType.Float:
|
|
return token.ToObject<float>();
|
|
case JTokenType.Boolean:
|
|
return token.ToObject<bool>();
|
|
//case JTokenType.Object:
|
|
// return new DataObject((JObject)token);
|
|
//case JTokenType.Array:
|
|
// JArray array = (JArray)token;
|
|
// return new DataArray(array);
|
|
default:
|
|
return token.ToString();
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|