| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110 |
- using System.Globalization;
- using System.Text.Json;
- namespace Infrastructure.StockData;
- /// <summary>
- /// 한국수출입은행 OpenAPI AP01(현재환율) 응답 파서 — 순수 C# (System.Text.Json).
- /// 응답 골격: 최상위가 JSON 배열 [ { result, cur_unit, cur_nm, ttb, tts, deal_bas_r, bkpr, kftc_deal_bas_r, ... }, ... ].
- /// - result: 각 행의 조회 결과 코드 (1=성공, 2=DATA코드 오류, 3=인증코드 오류, 4=일일제한초과). result != 1 인 행은 skip.
- /// - 값은 전부 문자열, 천단위 콤마 포함 가능("1,392.50"). ttb/tts 는 미제공 시 빈값 → null. deal_bas_r 파싱 실패는 skip.
- /// - cur_unit 은 "USD" / "JPY(100)" / "IDR(100)" 처럼 "(100)" 단위 표기가 붙을 수 있으므로 원문 그대로 보존한다.
- /// koreaexim quirk: 주말/공휴일·영업일 오전(~11시) 이전엔 빈 배열([]) 을, 키/파라미터 오류 시 result 2~4 또는 빈 응답이 온다 →
- /// 어느 경우든 예외 없이 빈 목록을 돌려 배치가 "데이터 없음"으로 skip 하도록 한다.
- /// </summary>
- public static class KoreaEximExchangeParser
- {
- /// <summary>환율 1행 (searchdate 는 호출부가 채움 — 응답에 날짜 필드가 없음)</summary>
- public sealed record ExchangeRow(
- string CurrencyUnit,
- string CurrencyName,
- decimal DealBasRate,
- decimal? Ttb,
- decimal? Tts
- );
- public static IReadOnlyList<ExchangeRow> Parse(string json)
- {
- var rows = new List<ExchangeRow>();
- 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;
- }
- // result != 1 (성공 아님) 인 행은 건너뛴다 (2=DATA코드 오류 / 3=인증코드 오류 / 4=일일제한초과)
- if (GetInt(item, "result") != 1)
- {
- continue;
- }
- var curUnit = GetString(item, "cur_unit");
- var dealBasRate = ParseNullableDecimal(GetString(item, "deal_bas_r"));
- if (curUnit.Length == 0 || dealBasRate is null)
- {
- continue;
- }
- rows.Add(new ExchangeRow(
- curUnit,
- GetString(item, "cur_nm"),
- dealBasRate.Value,
- ParseNullableDecimal(GetString(item, "ttb")),
- ParseNullableDecimal(GetString(item, "tts"))
- ));
- }
- 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
- };
- }
- private static int GetInt(JsonElement element, string name)
- {
- if (!element.TryGetProperty(name, out var value))
- {
- return 0;
- }
- return value.ValueKind switch
- {
- JsonValueKind.Number => value.TryGetInt32(out var n) ? n : 0,
- JsonValueKind.String => int.TryParse(value.GetString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var s) ? s : 0,
- _ => 0
- };
- }
- /// <summary>환율/금액 문자열 → decimal (천단위 콤마 제거). "" 나 "-" 등 파싱 실패는 null.</summary>
- private static decimal? ParseNullableDecimal(string raw)
- {
- return decimal.TryParse(raw, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var value) ? value : null;
- }
- }
|