将key=value的字符串解析为Map

4
我是一名有帮助的助手,可以为您进行文本翻译。以下是需要翻译的内容:

我正在使用一个提供XML数据的API,并且我需要从其中一个标签中获取一个字符串形式的地图。例如:

例如:

Billable=7200,Overtime=false,TransportCosts=20$

我需要

["Billable"="7200","Overtime=false","TransportCosts"="20$"]

问题在于该字符串是完全动态的,因此可能会像这样:
Overtime=true,TransportCosts=one, two, three
Overtime=true,TransportCosts=1= 1,two, three,Billable=7200

所以我不能只按逗号和等号分割。有没有可能使用正则表达式将这样的字符串转换为映射?

目前为止,我的代码是:

private Map<String, String> getAttributes(String attributes) {
    final Map<String, String> attr = new HashMap<>();
    if (attributes.contains(",")) {
        final String[] pairs = attributes.split(",");
        for (String s : pairs) {
            if (s.contains("=")) {
                final String pair = s;
                final String[] keyValue = pair.split("=");
                attr.put(keyValue[0], keyValue[1]);
            }
        }
        return attr;
    }
    return attr;
}

感谢您的提前支持。

我认为你可以先用 = 分割,然后再用 , 分割来完成。这里进行一些单元测试会非常有用 :) - Arnaud Denoyelle
2
你的示例代码预期结果是什么?你已经尝试过什么?请展示你所尝试的样本代码和其结果。 - Shar1er80
你是如何识别字符串应该如何解析/分割的?你使用了哪些规则? - Pshemo
那么为什么不能只按逗号和等号分割呢?使用带有限制的 split 就可以解决 "TransportCosts=1= 1" 的问题。 - AxelH
它不能是“完全动态的”。你需要更多的规则,比如“所有键必须是字母”,或者“=的优先级高于,”,或者反过来。否则就会存在歧义。 - jingx
显示剩余3条评论
4个回答

3
您可以使用:

(\w+)=(.*?)(?=,\w+=|$)

请查看正则表达式演示

细节

  • (\w+) - 第1组:一个或多个单词字符
  • = - 一个等号
  • (.*?) - 第2组:除换行符以外的任意零个或多个字符,尽可能少
  • (?=,\w+=|$) - 正向先行断言要求当前位置右侧是逗号、1个或多个单词字符、等号或字符串结束位置。

Java代码:

public static Map<String, String> getAttributes(String attributes) {
    Map<String, String> attr = new HashMap<>();
    Matcher m = Pattern.compile("(\\w+)=(.*?)(?=,\\w+=|$)").matcher(attributes);
    while (m.find()) {
        attr.put(m.group(1), m.group(2));
    }
    return attr;
}

Java测试

String s = "Overtime=true,TransportCosts=1= 1,two, three,Billable=7200";
Map<String,String> map = getAttributes(s);
for (Map.Entry entry : map.entrySet()) {
    System.out.println(entry.getKey() + "=" + entry.getValue());
}

结果:

Overtime=true
Billable=7200
TransportCosts=1= 1,two, three

@FilipeR。补充Wiktor的回答,使用Wiktor提供的模式,可以使用String.replaceAll(String pattern, String replacement)实现相同的功能。 s = s.replaceAll("(\w+)=(.*?)(?=,\w+=|$)", ""$1"="$2""); $1和$2是模式中的捕获组。 - Shar1er80

1
我注意到的第一件事是,使用您提供的数据很难识别分隔符,但似乎可以通过查找逗号后面跟着一个大写字母来确定每个字段的分隔符。
这使得可以使用正则表达式将分隔符更改为易于识别的内容,例如String.replaceAll("(?<=,)([A-Z])", ",$1")。现在您将拥有一个可识别的分隔符(,,)并可以拆分数据以插入所需的引号。
类似于以下内容:
public class StackOverflow {
    public static void main(String[] args) {
        String [] data = {
                "Overtime=true,TransportCosts=one, two, three",
                "Overtime=true,TransportCosts=1= 1,two, three,Billable=7200"
        };

        for (int i = 0; i < data.length; i++) {
            data[i] = data[i].replaceAll("(?<=,)([A-Z])", ",$1");
            String[] pieces = data[i].split(",,");
            for (int j = 0; j < pieces.length; j++) {
                int equalIndex = pieces[j].indexOf("=");
                StringBuilder sb = new StringBuilder(pieces[j]);
                // Insert quotes around the = sign
                sb.insert(equalIndex, "\"");
                sb.insert(equalIndex + 2, "\"");
                // Insert quotes at the beginning and end of the string
                sb.insert(0, "\"");
                sb.append("\"");
                pieces[j] = sb.toString();              
            }

            // Join the pieces back together delimited by a comma
            data[i] = String.join(",", pieces);
            System.out.println(data[i]);
        }
    }
}

结果
"Overtime"="true","TransportCosts"="one, two, three"
"Overtime"="true","TransportCosts"="1= 1,two, three","Billable"="7200"

0

另一种正则表达式,我认为更简单:([^,]+=[^=]+)(,|$)

([^,]+=[^=]+) → 由以下组成的组:除逗号外的任何内容,后跟1个等号,后跟任何不是等号的内容...
(,|$) → ...由逗号或行尾分隔

测试:

public static void main(String[] args) {
    Pattern pattern = Pattern.compile("([^,]+=[^=]+)(,|$)");

    String test1 = "abc=def,jkl,nm=ghi,egrh=jh=22,kdfka,92,kjasd=908@0982";
    System.out.println("Test 1: "+test1);
    Matcher matcher = pattern.matcher(test1);
    while (matcher.find()) {
        System.out.println(matcher.group(1));
    }
    System.out.println();
    String test2 = "Overtime=true,TransportCosts=1= 1,two, three,Billable=7200";
    System.out.println("Test 2: "+test2);
    matcher = pattern.matcher(test2);
    while (matcher.find()) {
        System.out.println(matcher.group(1));
    }
}

输出:

测试1:abc=def,jkl,nm=ghi,egrh=jh=22,kdfka,92,kjasd=908@0982
abc=def,jkl
nm=ghi
egrh=jh=22,kdfka,92
kjasd=908@0982

测试2:Overtime=true,TransportCosts=1= 1,two,three,Billable=7200
Overtime=true
TransportCosts=1= 1,two,three
Billable=7200


-1

我看到这段代码使用了Guava

import com.google.common.base.Splitter;


/**
 *  parse string 'prop1=val1; prop2=val2' to map
 */
 public static Map<String, String> parseMap(final String keyValueString) {
     if (StringUtils.isEmpty(keyValueString)) return Collections.emptyMap();

      return Splitter.on(";")
            .trimResults()
            .withKeyValueSeparator('=')
            .split(keyValueString);
}

需要注意的是,Idea会显示一个警告,因为Splitter被注释为com.google.common.annotations.Beta。这并不是什么坏事,但在更新guava库版本时可能需要一些工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接