JSON 파싱을 위한 문자열 자르기

알고리즘은 아니고 난해한 문제인데 이런문제를 코딩도장 회원님들은 어떻게 해결하시는지 한수 배우고 싶어 올려봅니다.

정황 :

서버에서 JSON 타입의 데이터가 담긴 Array 형태의 문자열을 클라이언트 도구로 전달해 줍니다. 이때 서버에서 설정한 버퍼( ex)32 byte )의 양만큼 JSON 문자열이 Stream 형태로 전달되는데 다음과 같이 블록단위로 끊어서 전달되는것이 아닌 이전에 보낸데이터와 추가로 보내는 데이터가 규칙없이 버퍼길이만큼 중첩되어 매번 보내집니다.

서버에서 전달되는 문자열 :

1st : '[ { "seq" : 0, "content" : "abcd"'
2nd : '[ { "seq" : 0, "content" : "abcd" }, { "seq" : 1, "content" : "d"'
3rd : '[ { "seq" : 0, "content" : "abcd" }, { "seq" : 1, "content" : "dsfswde" }, { "seq" : 2, "content"'
.
.
.
last : '[ { "seq" : 0, "content" : "abcd" }, ......................{ "seq" : n, "content" : "rtyy" } ]'

위 예처럼 들어온 문자열을 다음과 같이 JSON 객체로 인스턴스화 될 수 있도록(언어별 제공되는 JSON Parser 로 처리할 수 있도록) 문자열을 최소 블록별로 자르고 중복전송되는 데이터는 버리고 Array 형태로 만들어 줘야 합니다.

결과물 :

1st : X
2nd : [ { "seq" : 0, "content" : "abcd" } ]
3rd : [ { "seq" : 1, "content" : "dsfswde" } ]
.
.
.
last : [ { "seq" : n, "content" : "rtyy" } ]

감사합니다.

문자열 JSON
※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.

7개의 풀이가 있습니다.

VB.NET

    Sub Main()
        Dim arr As New List(Of String)

        Dim json As String = My.Computer.FileSystem.ReadAllText("C:\Users\진용\Desktop\example jons.txt")

        Dim arD() As String = Split(json, "[")

        For i As Integer = 1 To arD.Length - 1
            Dim bArr As New List(Of String)
            Dim d() As String = Split(arD(i), "{")

            For j As Integer = 0 To d.Length - 1
                If d(j).Contains("}") Then
                    Dim itm As String = String.Format("{{ {0} }}", Trim(Split(d(j), "}")(0)))

                    If Not arr.Contains(itm) Then
                        arr.Add(itm)
                        bArr.Add(itm)
                    End If
                End If
            Next

            Console.WriteLine(String.Format("[ {0} ]", String.Join(", ", bArr)))
        Next

        Console.ReadLine()
    End Sub

위 데이터를 기준으로 파싱해보았습니다.

※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.
$parser = new Parse();
$parser->parse_data('[ { "seq" : 0, "content" : "abcd"');
$parser->parse_data('[ { "seq" : 0, "content" : "abcd" }, { "seq" : 1, "content" : "d"');
$parser->parse_data('[ { "seq" : 0, "content" : "abcd" }, { "seq" : 1, "content" : "dsfswde" }, { "seq" : 2, "content"');
$parser->parse_data('[ { "seq" : 0, "content" : "abcd" }, { "seq" : 1, "content" : "dsfswde" }, { "seq" : 2, "content" : "rtyy" }');
$parser->printJson();

class Parse {
    private $pre_data   = "";
    private $json       = array();

    function parse_data($data) {
        $data = substr($data, 0, strrpos($data, "}") + 1);
        if (!$data) return false;

        $del_str = $this->pre_data;
        $this->pre_data = $data;
        $data = str_replace($del_str, "", $data);


        $data = preg_replace("/\\[|\s/", "", $data);
        $data = explode("},{", $data);
        foreach ($data as $value) {
            $value = preg_replace("/\\{|\\}|\\,/", "", $value);
            if ($value) $this->json[] = "[{".$value."}]";
        }
    }

    function printJson() {
        echo "<xmp>"; var_dump($this->json); echo "</xmp>";
    }
}
※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.

s477.txt

[ { "seq" : 0, "content" : "abcd"
[ { "seq" : 0, "content" : "abcd" }, { "seq" : 1, "content" : "d"
[ { "seq" : 0, "content" : "abcd" }, { "seq" : 1, "content" : "dsfswde" }, { "seq" : 2, "content"
[ { "seq" : 0, "content" : "abcd" }, { "seq" : 1, "content" : "dsfswde" }, { "seq" : 2, "content" : "rtyy" } ]

s477.py

prev_complete='[ '
for line in file('s477.txt'):
    p=prev_complete
    while p and p[0]==line[0]:p=p[1:];line=line[1:]
    buff=''
    for c in line:
        if c=="{" : inner = True
        elif c=="}" : inner = False ;break
        elif inner : buff += c
    if not inner :
        print '[ {'+buff+'} ]'
        prev_complete += '{'+buff+'}, '
※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.

일단 들어오는 JSON 포맷이 예시와 같다고만 가정하면 들어오는데로 스캔 위치를 계속 밀어나가면서 중괄호 사이를 추출합니다. (중괄호는 중첩될 수 있다고 가정하되, 올바른 포맷으로 들어온다고 가정합니다.)

def do():
    i = 0
    last_i = 0
    st = []
    result = []
    while True:
        s = input()
        if not s:
            break
        while i < len(s):
            if s[i] == '{':
                last_i = i
                st.append('{')
            elif s[i] == '}':
                if st[-1] == '{':
                    st.pop()
                    if len(st) == 0:
                        result.append(s[last_i:i+1])
            i += 1
    return result
do()

그런데, 데이터가 계속 중첩되어 들어온다면 최종 데이터만 받아서 처리하면 되지 않을까도 싶네요.

※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.

Ruby

정규식으로 json 배열 획득 > set에 입력 > 버퍼별로 set에 새로 추가된 요소를 map.

require 'set'
rcved = ->buffers,sets=Set.new {
  buffers.map {|s| s.gsub(/{(.|^})+?}/).to_a.delete_if {|e| !sets.add?(e) } } }

Test

buffers = [ '[ { "seq" : 0, "content" : "abcd"',
            '[ { "seq" : 0, "content" : "abcd" }, { "seq" : 1, "content" : "d"',
            '[ { "seq" : 0, "content" : "abcd" }, { "seq" : 1, "content" : "dsfswde" }, { "seq" : 2, "content"' ]
expected = [ [], 
             ['{ "seq" : 0, "content" : "abcd" }'],
             ['{ "seq" : 1, "content" : "dsfswde" }'] ]
expect( rcved[buffers] ).to eq expected

Output

#=> puts rcved[buffers] 빈 배열은 stdout에 출력되지 않음.
{ "seq" : 0, "content" : "abcd" }
{ "seq" : 1, "content" : "dsfswde" }
※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.

미완성 입력을 처리할 수 있도록 파서를 만드는 것도 좋을 것 같습니다.

약식으로 객체, 배열, 정수, 문자열 만으로 구성된 JSON을 파싱한다면..

결과를 저장할 때 complete 필드에 1(완성), 0(미완성)으로 표시하는 식이죠.

typedef struct arr_t arr_t;
typedef struct obj_t obj_t;

typedef struct json_t json_t;
struct json_t {
    int type;
    union {
        char* s;
        int    n;
        arr_t* a;
        obj_t* o;
    } value;
    int complete;
};

최상위 파서 json은 첫 글자를 기준으로 arr, obj, num, str으로 분기합니다.

json_t* json(context_t *c) {
    spaces(c);
    if (*c->s == '[') {
        c->s++;
        return arr(c);
    }
    if (*c->s == '{') {
        c->s++;
        return obj(c);
    }
    if (*c->s == '"') {
        c->s++;
        return str(c);
    }
    if (*c->s == '-' || isdigit(*c->s)) {
        return num(c);
    }
    assert(!"[, {, \", num required");
}

typedef struct context_t context_t;
struct context_t {
    const char* s;
};

void spaces(context_t* c) {
    while (*c->s == ' ')
        c->s++;
}

가장 쉬운 num의 경우는 일단 시작하면 미완성을 판단하기 어려워요. -만 나오고 마는 경우 정도?

json_t* num(context_t* c) {
    int sign = 1;
    if (*c->s == '-') {
        sign = -1;
        c->s++;
    }
    if (*c->s == 0) {
        return new_json_num(0, 0);
    }

    int value = 0;
    while (isdigit(*c->s)) {
        value *= 10;
        value += *c->s++ - '0';
    }
    return new_json_num(value, 1);
}

str은 다음 따옴표가 나와야지 완성됩니다. (약식이라 quoted string을 제대로 지원하지는 않아요)

json_t* str(context_t* c) {
    const char* begin = c->s;
    while (*c->s && *c->s != '"') {
        c->s++;
    }
    if (*c->s == 0) {
        return new_json_str(begin, c->s, 0);
    }
    return new_json_str(begin, c->s++, 1); // skip "
}

arr은 ]가 나올때까지 ,로 구분된 json의 목록입니다.

json_t* arr(context_t* c) {
    spaces(c);
    arr_t *a = new_arr();
    while (*c->s && *c->s != ']') {
        json_t* value = json(c);
        arr_add(a, value);
        spaces(c);
        if (*c->s == 0) {
            break;
        }
        if (*c->s == ',') {
            c->s++;
        } else if (*c->s != ']'){
            assert(!", or ] required");
        }
    }
    if (*c->s == 0) // incomplete
        return new_json_arr(a, 0);
    c->s++; // skip ]
    return new_json_arr(a, 1);
}

가장 복잡한 obj는 ':'로 구분된 key-value를 저장해야죠.

json_t* obj(context_t* c) {
    spaces(c);
    obj_t *o = new_obj();
    while (*c->s && *c->s != '}') {
        json_t* key = json(c);
        assert(key->type == STR);
        arr_add(o->keys, key);

        spaces(c);
        if (*c->s == 0) {
            break;
        }
        if (*c->s == ':') {
            c->s++;
        } else {
            assert(!": required");
        }

        spaces(c);
        if (*c->s == 0) {
            break;
        }

        json_t* value = json(c);
        arr_add(o->values, value);

        spaces(c);

        if (*c->s == 0) {
            break;
        }
        if (*c->s == ',') {
            c->s++;
            spaces(c);
        } else if (*c->s != '}') {
            assert(!", or } required");
        }
    }
    if (*c->s == 0) // incomplete
        return new_json_obj(o, 0);
    c->s++; // skip }
    return new_json_obj(o, 1);
}

이제 complete 필드를 이용하여 완성된 값인지 아닌지 판별할 수 있습니다. 문제 상황에서는 배열의 요소들이 object인데 seq 필드가 유일하게 구분가능한 값을 가진다면 쉽게 이미 처리한 것과 아닌것을 구분할 수 있을 것 같습니다.

※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.
class JsonStr():
    def __init__(self):
        self.pos = 0
        self.str = ''
    def get(self, str):
        rarr = []
        r = ''
        pos = start = self.pos;
        is_str = False
        is_bs = False
        for x in str[start:]:
            pos+=1
            if not is_str and x == '{':
                r += x
            elif r == '':
                continue
            elif not is_str and x == '}':
                r += x
                rarr += [r]
                r = ''
                self.pos = pos
                continue
            else:
                r += x
                if is_str and x == '\\':
                    is_bs = not is_bs
                elif not is_bs and (x == "'" or x == '"'):
                    isstr = not is_str

        print('[' + ','.join(rarr) + ']' if rarr else '')


json = JsonStr()
json.get('[ { "seq" : 0, "content" : "abcd"')
json.get('[ { "seq" : 0, "content" : "abcd" }, { "seq" : 1, "content" : "d"')
json.get('[ { "seq" : 0, "content" : "abcd" }, { "seq" : 1, "content" : "dsfswde" }, { "seq" : 2, "content"}')
json.get('[ { "seq" : 0, "content" : "abcd" }, { "seq" : 1, "content" : "dsfswde" }, { "seq" : 2, "content"}, \
{ "seq" : 3, "content"}, { "seq" : 4, "content4" ')

Python 3.5.2에서 작성하였습니다.

※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.

풀이 작성

※ 풀이작성 안내
  • 본문에 코드를 삽입할 경우 에디터 우측 상단의 "코드삽입" 버튼을 이용 해 주세요.
  • 마크다운 문법으로 본문을 작성 해 주세요.
  • 풀이를 읽는 사람들을 위하여 풀이에 대한 설명도 부탁드려요. (아이디어나 사용한 알고리즘 또는 참고한 자료등)
  • 작성한 풀이는 다른 사람(빨간띠 이상)에 의해서 내용이 개선될 수 있습니다.
목록으로
코딩도장

코딩도장은 프로그래밍 문제풀이를 통해서 코딩 실력을 수련(Practice)하는 곳입니다.

JSON x 2
문자열 x 1
연관 문제
코딩초보, 2017/03/02 16:46

언어별 풀이 현황
전 체 x 7
기 타 x 2
php x 1
python x 3
ruby x 1