【问题标题】:Why does this tokenizer return incorrect values?为什么这个分词器返回不正确的值?
【发布时间】:2019-12-17 12:01:57
【问题描述】:

当标记一个 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.标识符("username":)
3. STRING_LITERAL ("Azoraqua")
4. 代币 (,)
5. 标识符 ("age")
6. INTEGER_LITERAL (21)
7. END_OBJECT (})

我该如何解决这个问题?

【问题讨论】:

    标签: java json regex tokenize


    【解决方案1】:

    问题最有可能出在这一行:

        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 几乎肯定会遇到类似的问题。它也是贪婪的。您可以使用相同的解决方案修复它,即使 .* 不贪婪。

    【讨论】:

    • 现在,输出发生了变化,它在技术上是正确的,但并不是我想要的那样:Token {type=BEGIN_OBJECT, token='{'}Token {type=STRING_LITERAL, token='"username"'}Token {type=COLON, token=':'}Token {type=STRING_LITERAL, token='"Azoraqua"'}Token {type=COMMA, token=','}Token {type=STRING_LITERAL, token='"age"'}Token {type=COLON, token=':'} Token {type=INTEGER_LITERAL, token='21'} Token {type=END_OBJECT, token='}'} 除了读起来有点……字面意思。
    • 我不确定如何回答这个问题,因为缺少 Token 类的相关来源。但基本上你的 LITERAL 模式和 IDENTIFIER 模式是相同的。唯一的区别是 IDENTIFIER 末尾有一个冒号。因此,如果 LITERAL 在 标识符之前进行了测试,那么 LITERAL 将首先匹配。
    • 如果这对您有所帮助,如果您能接受答案,那就太好了。您可以随时使用新版本的代码、预期结果和实际结果提出一个新问题(而不是更新这个问题)。
    • 实际上,我认为我对结果很满意,虽然不是我想要的,但它确实满足了我的需要。谢谢@GMc。
    猜你喜欢
    • 1970-01-01
    • 2013-03-08
    • 2014-04-02
    • 1970-01-01
    • 1970-01-01
    • 2013-12-04
    • 1970-01-01
    • 2018-10-19
    • 1970-01-01
    相关资源
    最近更新 更多