| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697 |
- using System.Globalization;
- using System.Text.Json;
- namespace Infrastructure.StockData;
- /// <summary>
- /// 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 (해당 시점 미적재, 다음 실행 재시도).
- /// </summary>
- public static class KosisParser
- {
- /// <summary>KOSIS 데이터 1행. Value 파싱 실패(빈값/"-") 행은 목록에서 제외되므로 여기 Value 는 항상 유효.</summary>
- public sealed record Row(
- string Period,
- decimal Value,
- string? Unit,
- string ItemName,
- string ClassCode,
- string ClassName
- );
- public static IReadOnlyList<Row> Parse(string json)
- {
- var rows = new List<Row>();
- 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
- };
- }
- /// <summary>수치 문자열 → decimal (천단위 콤마 제거). ""·"-"·"X" 등 파싱 실패는 null.</summary>
- private static decimal? ParseNullableDecimal(string raw)
- {
- return decimal.TryParse(raw, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var value) ? value : null;
- }
- }
|