본문 바로가기

C#/근웹 연대기

c#으로 근본 없는 웹서버 개발기 14 : json parse 만들기

⚠WARNING
ASP.NET에 대한 포스팅이 아닙니다
나가실 문은 오른쪽 하단입니다

 

 

파싱 작업이라는 것은 언제나 몇 번이라도. 적지 않는 정신력을 소모한다.

단순히 문자열을 틀에 맞추는 작업일 뿐인데, 

깔끔하게 처리하기가 여간 까다롭다.

그런데도 마음속 어딘가에서는 끌리는 매력을 느낀다.

아마도 첫 직장 첫 제안으로 했던 풋풋함이 심층 깊은 골목의 한켠에 녹아들어 있다고 납득하고 싶다.

 

그날 그 여름날에는.. 반복적인 SQL을 입력하는 단순 노가다에 어떠한 패턴을 느껴버리고 나서, 어지간히도 자동화를 외치고 싶었다.

신입이었던 나는 정규식도, 문자열을 조작하는 메서드도 그러한 테크닉도 몰랐다. (물론 지금도 잘 모른다.)

그냥 IndexOf 로 상황에 맞는 단어가 나오면  거기에 맞게 문자열을 바꾸는 방식이었다.

학교에서는 자바라는것을 공부했는데, 직장에서는 C#이라는 것으로 일을 했기 때문이었다.

내가 자바를 공부 했을 적에는 자바 하나만으로 온 세상 코드들을 다 이해할 수 있을 거 같았기 때문에 다른 언어에는 관심이 없었다.

C#에 대해 조ー또(ちょっと)모르는 나에게 왜 그런 업무를 부여했는지 지금은 어렴풋이 알 것 같지만, 당신에는 너무 암 껏도 몰라서. 뭐라도 해야겠다는 압박감에 패기 넘치는 제안을 한 것이다.

그 일로 한 80% 이상이 자동화되어서, 더욱 패기 넘치게 된 것으로 인해 안 좋은 역사가 남긴 했지만 말이다.

그렇다

개발은 겸손이 중요하다.

그렇다

잘한다 잘한다 소리 몇 번 들으면 감당하기 힘든 업무가 쏟아지기 때문이다.

...

 

잡설은 여기까지 하고, 전편에 이어 파싱 하는 부분을 다음과 같이 작성하였다.

 

    public static J parse(string str)
    {
        return fromString(str, 0).rt;
    }
    static readonly Exception syntaxErr = new ArgumentException("Syntax Error.");
    static (J rt,int ix) fromString(string str, int ix)
    {
        void skipEmpty()
        {
            for (; ix < str.Length && Char.IsWhiteSpace(str[ix]); ix++) ;
            if (ix == str.Length) throw syntaxErr;
        }

        skipEmpty();

        J rt = str[ix] switch
        {
            '[' => new JL(),
            '{' => new JO(),
            _ => throw syntaxErr
        };
        ix++;

        bool keyPhase = rt is JO;

        string next()
        {
            skipEmpty();
            if (str[ix] is '[' or '{') return ""+ str[ix];
            int st = ix;
            if (str[ix] == '"')
            {
                ix++;
                for(bool esc = false; ix < str.Length; ix++)
                {
                    var c = str[ix];
                    if (c == '"' && !esc) break;
                    esc = !esc && c == '\\';
                }
                if (ix == str.Length) throw syntaxErr;
                ix++;
            }

            string ckstr = "," + (rt is JL ? "]" : keyPhase ? ":}" : "}");
            ix = str.IndexOfAny((" " + ckstr).ToArray(), ix);
            if (ix == -1) throw syntaxErr;

            var rtstr = str[st..ix];
            skipEmpty();
            ix = str.IndexOfAny(ckstr.ToArray(), ix);
            if (ix == -1) throw syntaxErr;

            return rtstr;
        }
       
        bool endCheck(char c)
        {
            return (rt, c) switch
            {
                (JO, '}') => true,
                (JL, ']') => true,
                (_ , ',') => false,
                _ => throw syntaxErr
            };
        }

        for(string key = "?" ; ix < str.Length; ix++)
        {
            string tk =  next();
            if (tk.Length == 0) break;

            char c = str[ix];
            bool isStr = tk.StartsWith("\"");
            bool isSub = tk is "[" or "{";
           
            if (isSub)
            {
                if(keyPhase) throw syntaxErr;
                (J child, ix) = fromString(str, ix);
                rt.Add(key, child);
                ix++;
                tk = next();
                c = str[ix];
                if (tk.Length > 0)  throw syntaxErr;

                keyPhase = rt is JO;
                if (endCheck(c)) break;

                continue;
            }

            tk = Unescape(tk).Trim();
            object value;

            if (isStr) value = tk[1..^1];
            else
            {
                int ival;
                double dval;
                value = tk switch
                {
                    "null" => null,
                    _ when int.TryParse(tk, out ival) => ival,
                    _ when double.TryParse(tk, out dval) => dval,
                    _ => tk
                };
            }

            if (rt is JO)
            {
                if(keyPhase && (!isStr || c != ':')) throw syntaxErr;

                if (keyPhase) key = value.ToString();
                else rt.Add(key, value);

                keyPhase = !keyPhase;

                if (keyPhase == false) continue;
            }
            else rt.Add(value);

            if (endCheck(c)) break;
        }

        if (ix == str.Length) throw syntaxErr;

        return (rt, ix);
    }

보다시피, 사전에 언급했듯이 꽤 손이 많이 갔다.

 

그리고 테스트 소스를 작성하였다.

J j = J.O((1, J.L(1, 2, J.L(1.1, 1.2, 1.3), 4, "5")), ("k\"ey", "value"));

var jstr = j.stringify();
Console.WriteLine(jstr);

var newObj = J.parse(jstr);
Console.WriteLine(newObj.stringify());

 

그리고 최종 확인.

 

이것으로 추억과 함께한 JSON 파트를 마치도록 하겠다.

 

UnEscape :

2022.03.08 - [C#/예제 코드] - c# 자바스크립트 문자열 encode & decode (escape)