using System.Globalization;
using System.Text.Json;
namespace Infrastructure.StockData;
///
/// KOSIS statisticsData.do JSON 응답 파서 — 순수 C# (System.Text.Json).
/// 응답 골격: 성공 시 최상위 JSON 배열 [ { PRD_DE, DT, UNIT_NM, ITM_NM, C1, C1_NM, TBL_ID, ... }, ... ].
/// 오류 시엔 배열이 아닌 객체 { "err":"21", "errMsg":"..." } → 예외 없이 빈 목록 반환 (koreaexim 파서와 동일한 방어).
/// - PRD_DE(수록시점) → Period, DT(수치) → Value, UNIT_NM(단위) → Unit, ITM_NM/C1/C1_NM → 계열 식별용 보조.
/// - KOSIS quirk: 통계표 하나에 여러 분류(C1 시도별/지수별)가 섞여 오므로, 계열 특정은 상위(수집 배치)가 C1 필터로 수행한다.
/// - DT 는 문자열로 오며 미고시 시점은 ""·"-" 로 온다 → Value 파싱 실패 행은 skip (해당 시점 미적재, 다음 실행 재시도).
///
public static class KosisParser
{
/// KOSIS 데이터 1행. Value 파싱 실패(빈값/"-") 행은 목록에서 제외되므로 여기 Value 는 항상 유효.
public sealed record Row(
string Period,
decimal Value,
string? Unit,
string ItemName,
string ClassCode,
string ClassName
);
public static IReadOnlyList Parse(string json)
{
var rows = new List();
if (string.IsNullOrWhiteSpace(json))
{
return rows;
}
using var doc = JsonDocument.Parse(json);
if (doc.RootElement.ValueKind != JsonValueKind.Array)
{
return rows;
}
foreach (var item in doc.RootElement.EnumerateArray())
{
if (item.ValueKind != JsonValueKind.Object)
{
continue;
}
var period = GetString(item, "PRD_DE");
if (period.Length == 0)
{
continue;
}
var value = ParseNullableDecimal(GetString(item, "DT"));
if (value is null)
{
continue;
}
var unit = GetString(item, "UNIT_NM");
rows.Add(new Row(
period,
value.Value,
unit.Length == 0 ? null : unit,
GetString(item, "ITM_NM"),
GetString(item, "C1"),
GetString(item, "C1_NM")));
}
return rows;
}
private static string GetString(JsonElement element, string name)
{
if (!element.TryGetProperty(name, out var value))
{
return string.Empty;
}
return value.ValueKind switch
{
JsonValueKind.String => value.GetString()?.Trim() ?? string.Empty,
JsonValueKind.Number => value.GetRawText(),
_ => string.Empty
};
}
/// 수치 문자열 → decimal (천단위 콤마 제거). ""·"-"·"X" 등 파싱 실패는 null.
private static decimal? ParseNullableDecimal(string raw)
{
return decimal.TryParse(raw, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var value) ? value : null;
}
}