首页 > 解决方案 > 为什么这个分词器返回不正确的值?

问题描述

当标记一个 JSON 字符串时,它返回一个不正确的值,就像它一次连接多个值(即"username": "Azoraqua", "age": }(它应该分别是 IDENTIFIER(2 次)和 STRING_LITERAL(1 次)),请注意它确实返回age数字作为它自己的令牌(分别为 INTEGER_LITERAL)。

我尝试了几种方法来实现正确的行为:
- 更改一些与 IDENTIFER 和 STRING_LITERAL 相关的正则表达式。
- 更改一些实际的标记化逻辑。

private static final Set<TokenData> tokenDatas = new LinkedHashSet<>();

static {
    tokenDatas.add(new TokenData(Pattern.compile("^(,:)"), TokenType.TOKEN));
    tokenDatas.add(new TokenData(Pattern.compile("^(\\{)"), TokenType.BEGIN_OBJECT));
    tokenDatas.add(new TokenData(Pattern.compile("^(})"), TokenType.END_OBJECT));
    tokenDatas.add(new TokenData(Pattern.compile("^(\\[)"), TokenType.BEGIN_ARRAY));
    tokenDatas.add(new TokenData(Pattern.compile("^(])"), TokenType.END_ARRAY));
    tokenDatas.add(new TokenData(Pattern.compile("^(\".*\":)"), TokenType.IDENTIFIER));
    tokenDatas.add(new TokenData(Pattern.compile("^(\".*\")"), TokenType.STRING_LITERAL, (s) -> s.substring(1, s.length() - 1)));
    tokenDatas.add(new TokenData(Pattern.compile("^((-)?[0-9]+)"), TokenType.INTEGER_LITERAL));
    tokenDatas.add(new TokenData(Pattern.compile("^((-)?[0-9]*(\\.)[0-9]+)"), TokenType.DOUBLE_LITERAL));
    tokenDatas.add(new TokenData(Pattern.compile("^(true|false)", Pattern.CASE_INSENSITIVE), TokenType.BOOLEAN_LITERAL));
}
@Override
public Token next() {
    str = str.trim();

    if (pushback) {
        pushback = false;
        return lastToken;
    }

    if (str.isEmpty()) {
        return (lastToken = new Token(TokenType.EMPTY, ""));
    }

    for (TokenData data: tokenDatas) {
        Matcher matcher = data.pattern.matcher(str);

        if (matcher.find()) {
            String token = matcher.group().trim();
            str = matcher.replaceFirst("");

            if (data.action != null) {
                token = data.action.apply(token);
            }

            return (lastToken = new Token(data.type, token));
        }
    }

    throw new IllegalStateException("Could not parse " + str);
}

当输入为时{"username": "Azoraqua", "age": 21},输出应为:
1. BEGIN_OBJECT ( {)
2. IDENTIFIER ( "username":)
3. STRING_LITERAL ( "Azoraqua")
4. TOKEN ( ,)
5. IDENTIFIER ( "age")
6. INTEGER_LITERAL ( 21)
7. END_OBJECT ( })

我该如何解决这个问题?

标签: javajsonregextokenize

解决方案


问题很可能出在这一行:

    tokenDatas.add(new TokenData(Pattern.compile("^(\".*\":)"), TokenType.IDENTIFIER));

正则表达式是贪婪的。这意味着他们将尽可能多地匹配。

因此,对于这样的字符串:

"username": "Azoraqua", "age": 21 }

.*\":正则表达式的部分将从“用户名”中的 u 开始匹配所有字符,包括最后一个可能\":出现的字符,该字符出现在 21 前面的“空格”字符之前。

尝试使用“?”使您的正则表达式不贪婪。修饰符。

    tokenDatas.add(new TokenData(Pattern.compile("^(\".*?\":)"), TokenType.IDENTIFIER));

您可能还希望允许可选的空格

    tokenDatas.add(new TokenData(Pattern.compile("^(\".*?\"\s*:)"), TokenType.IDENTIFIER));

您几乎肯定会遇到类似的问题TokenType.STRING_LITERAL。它也是贪婪的。您可以使用相同的解决方案修复它,即使.*非贪婪。


推荐阅读