본문 바로가기
Issue

[Issue] XContentParseException: failed to parse filed, JsonParseException : Duplicate field 'positionLength' 해결

by 잭피 2021. 12. 17.

에러 분석

SpringBoot (Java Code)

현재 엘라스틱서치 클라이언트로 RestHighLevelClient를 사용하고 있다

해당 라이브러리에서 형태소 분석을 위해 한 API가 analyze()을 사용하고 있다

AnalyzeRequest request = AnalyzeRequest.withIndexAnalyzer(indexName, analyzerName, message);
request.explain(true);

AnalyzeResponse response = restHighLevelClient.indices().analyze(request, RequestOptions.DEFAULT);

 

그런데 특정 단어에서 IOException이 발생하였다

(분석기는 nori를 사용했다)

java.io.IOException: Unable to parse response body for Response{requestLine=GET /chat-autocomplete/_analyze HTTP/1.1, host=http://localhost:9200, response=HTTP/1.1 200 OK}
	at org.elasticsearch.client.RestHighLevelClient.internalPerformRequest(RestHighLevelClient.java:1533) ~[elasticsearch-rest-high-level-client-7.6.2.jar:7.6.2]
	... (생략)
Caused by: org.elasticsearch.common.xcontent.XContentParseException: [1:2579] [analyze_response] failed to parse field [detail]
	at org.elasticsearch.common.xcontent.ObjectParser.parseValue(ObjectParser.java:429) ~[elasticsearch-x-content-7.6.2.jar:7.6.2]
	... (생략)
Caused by: org.elasticsearch.common.xcontent.XContentParseException: [1:2579] [detail] failed to parse field [tokenfilters]
	... 69 more
Caused by: org.elasticsearch.common.xcontent.XContentParseException: [1:2579] [token_list] failed to parse field [tokens]
	... 69 more
Caused by: com.fasterxml.jackson.core.JsonParseException: Duplicate field 'positionLength'
 at [Source: (org.apache.http.nio.entity.ContentInputStream); line: 1, column: 2606]
	... 69 more

 

익셉션이 발생하는 위치를 찾아보았다

 

[1:2579] [analyze_response] failed to parse field [detail]

[1:2579] [detail] failed to parse field [tokenfilters]

[1:2579] [token_list] failed to parse field [tokens]

// ObjectParser.java
private void parseValue(XContentParser parser, FieldParser fieldParser, String currentFieldName, Value value, Context context)
            throws IOException {
        try {
            fieldParser.parser.parse(parser, value, context);
        } catch (Exception ex) {
            throw new XContentParseException(parser.getTokenLocation(),
                "[" + name  + "] failed to parse field [" + currentFieldName + "]", ex);
        }

 

Caused by: com.fasterxml.jackson.core.JsonParseException:
 Duplicate field 'positionLength'

public void setCurrentName(String name) throws JsonProcessingException {
        _currentName = name;
        if (_dups != null) { _checkDup(_dups, name); }
    }

private void _checkDup(DupDetector dd, String name) throws JsonProcessingException {
        if (dd.isDup(name)) {
            Object src = dd.getSource();
            throw new JsonParseException(((src instanceof JsonParser) ? ((JsonParser) src) : null),
                    "Duplicate field '"+name+"'");
        }
    }

 

파싱이 제대로 이뤄지지 않았고, parser.nextToken()에서 _checkDup이 True가 되면서 JsonParseException이 발생했다

Json 값을 파싱하다 positionLength 필드 중복이 발생했다

 

 

Elasticsearch 

 

ES에 직접 조회해보았다

GET chat-autocomplete/_analyze
{
  "analyzer": "nori_analyzer",
  "text": "판타스틱",
  "explain": true
}

결과를 보니 몇몇 token에서 positionLength 필드가 중복되어 내려오고 있었다

positionLength 필드를 자바 코드에서 파싱하다가 중복이 발생해서 익셉션이 발생한 것이다

      {
        "name" : "nori_posfilter",
        "tokens" : [
          {
            "token" : "팬더",
            "start_offset" : 0,
            "end_offset" : 1,
            "type" : "SYNONYM",
            "position" : 0,
            "positionLength" : 2,
            "bytes" : "[ed 8c ac eb 8d 94]",
            "leftPOS" : null,
            "morphemes" : null,
            "posType" : null,
            "positionLength" : 2,
            "reading" : null,
            "rightPOS" : null,
            "termFrequency" : 1
          },
          {
            "token" : "판",
            "start_offset" : 0,
            "end_offset" : 1,
            "type" : "SYNONYM",
            "position" : 0,
            "bytes" : "[ed 8c 90]",
            "leftPOS" : null,
            "morphemes" : null,
            "posType" : null,
            "positionLength" : 1,
            "reading" : null,
            "rightPOS" : null,
            "termFrequency" : 1
          },
          {
            "token" : "판",
            "start_offset" : 0,
            "end_offset" : 1,
            "type" : "word",
            "position" : 0,
            "positionLength" : 2,
            "bytes" : "[ed 8c 90]",
            "leftPOS" : "NNG(General Noun)",
            "morphemes" : null,
            "posType" : "MORPHEME",
            "positionLength" : 2,
            "reading" : null,
            "rightPOS" : "NNG(General Noun)",
            "termFrequency" : 1
          },
          {
            "token" : "더",
            "start_offset" : 0,
            "end_offset" : 1,
            "type" : "SYNONYM",
            "position" : 1,
            "bytes" : "[eb 8d 94]",
            "leftPOS" : null,
            "morphemes" : null,
            "posType" : null,
            "positionLength" : 1,
            "reading" : null,
            "rightPOS" : null,
            "termFrequency" : 1
          },
          {
            "token" : "스틱",
            "start_offset" : 2,
            "end_offset" : 4,
            "type" : "word",
            "position" : 4,
            "bytes" : "[ec 8a a4 ed 8b b1]",
            "leftPOS" : "NNG(General Noun)",
            "morphemes" : null,
            "posType" : "MORPHEME",
            "positionLength" : 1,
            "reading" : null,
            "rightPOS" : "NNG(General Noun)",
            "termFrequency" : 1
          }
        ]
      }

이렇게 positionLength가 중복되어 내려오는 현상이 어떤 경우인지 찾아보았다

 

"판타스틱"을 nori 분석기로 형태소 분석을 해보면 이렇게 내려왔다

"판"이라는 토큰이 중복되고 있다

{
  "tokens" : [
    {
      "token" : "팬더",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "SYNONYM",
      "position" : 0,
      "positionLength" : 2
    },
    {
      "token" : "판",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "SYNONYM",
      "position" : 0
    },
    {
      "token" : "판",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "word",
      "position" : 0,
      "positionLength" : 2
    },
    {
      "token" : "더",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "SYNONYM",
      "position" : 1
    },
    {
      "token" : "스틱",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "word",
      "position" : 4
    }
  ]
}

 

사전을 확인해보니.

Fan의 동의어로 판, 팬더, 판더가 등록되어 있었다

판더는 따로 용어사전에 등록되어 있지 않았다

그러다보니 nori로 인해 "판" + "더"로 분해했다

이렇게 토큰이 중복되는 경우에 positionLength 필드가 중복되어져 내려오는 것을 확인하였다

해결

판더를 용어사전에 넣어주면 해결된다

그러면 nori 분석기는 "판더"를 그대로 토큰으로 인식한다.

GET chat-autocomplete/_analyze
{
  "analyzer": "nori_analyzer",
  "text": "판타스틱"
}

그리고 중복되는 토큰이 없다

{
  "tokens" : [
    {
      "token" : "팬더",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "SYNONYM",
      "position" : 0
    },
    {
      "token" : "판더",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "SYNONYM",
      "position" : 0
    },
    {
      "token" : "판",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "스틱",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "word",
      "position" : 3
    }
  ]
}

explain을 통해 확인해보면, positionLength가 1개만 내려온다

      {
        "name" : "nori_posfilter",
        "tokens" : [
          {
            "token" : "팬더",
            "start_offset" : 0,
            "end_offset" : 1,
            "type" : "SYNONYM",
            "position" : 0,
            "bytes" : "[ed 8c ac eb 8d 94]",
            "leftPOS" : null,
            "morphemes" : null,
            "posType" : null,
            "positionLength" : 1,
            "reading" : null,
            "rightPOS" : null,
            "termFrequency" : 1
          },
          {
            "token" : "판더",
            "start_offset" : 0,
            "end_offset" : 1,
            "type" : "SYNONYM",
            "position" : 0,
            "bytes" : "[ed 8c 90 eb 8d 94]",
            "leftPOS" : null,
            "morphemes" : null,
            "posType" : null,
            "positionLength" : 1,
            "reading" : null,
            "rightPOS" : null,
            "termFrequency" : 1
          },
          {
            "token" : "판",
            "start_offset" : 0,
            "end_offset" : 1,
            "type" : "word",
            "position" : 0,
            "bytes" : "[ed 8c 90]",
            "leftPOS" : "NNG(General Noun)",
            "morphemes" : null,
            "posType" : "MORPHEME",
            "positionLength" : 1,
            "reading" : null,
            "rightPOS" : "NNG(General Noun)",
            "termFrequency" : 1
          },
          {
            "token" : "스틱",
            "start_offset" : 2,
            "end_offset" : 4,
            "type" : "word",
            "position" : 3,
            "bytes" : "[ec 8a a4 ed 8b b1]",
            "leftPOS" : "NNG(General Noun)",
            "morphemes" : null,
            "posType" : "MORPHEME",
            "positionLength" : 1,
            "reading" : null,
            "rightPOS" : "NNG(General Noun)",
            "termFrequency" : 1
          }
        ]
      }

 

 

 

댓글