thurmc
thurmc

Reputation: 525

Best way to retrieve a value from a string java

If I am being passed a string that contains comma delimited key-value pairs like this

seller=1000,country="canada",address="123 1st st", etc.

There seems like there must be a better way than parsing then iterating through.

What is the best way to retreive a value from this string based on the key name in Java?

Upvotes: 1

Views: 7856

Answers (7)

tsauerwein
tsauerwein

Reputation: 6041

Since release 10 Google Guava provides a class MapSplitter which does exactly that kind of things:

Map<String, String> params = Splitter
    .on(",")
    .withKeyValueSeparator("=")
    .split("k1=v1,k2=v2");

Upvotes: 6

user177800
user177800

Reputation:

First thing you should use a CSV parsing library to parse the comma separated values. Correctly parsing CSV data isn't as trivial as it first seems. There are lots of good arguments to not reinvent that wheel.

This will also future proof your code and be code you don't have to test or maintain.

I know the temptation to do something like data.split(','); is strong, but it is fragile and brittle solution. For just one example, what if any of the values contain the ','.

Second thing you should do is then parse the pairs. Again the temptation to use String.split("="); will be strong, but it can be brittle and fragile if the right hand side of the = has an = in it.

I am not a blind proponent of regular expressions, but used with restraint they can be just the right tool for the job. Here is the regular expression to parse the name value pairs.

The regular expression ^(.*)\s?=\s?("?([^"]*)"?|"(.*)")$, click the regular expression to test it online interactively. This works even for multiple double quotes in the right hand side of the name value pair.

This will match only what is on the left side of the first = and everything else on the right hand side, and strip the optional " off the string values, while still matching the non-quoted number values.

Given a List<String> list of the encoded name value pairs.

final Pattern p = Pattern.compile("^(.*)\s?=\s?("?([^"]*)"?|"(.*)")$");
final Map<String, String> map = new HashMap<String, String>(list.size());
for (final String nvp : list)
{
    final Matcher m = p.matcher(nvp);
    m.matches();
    final String name = m.group(1);
    final String value = m.group(2);
    System.out.format("name = %s | value = %s\n", name, value);       
}

Upvotes: 0

Newtopian
Newtopian

Reputation: 7732

You can create your own CSV parser, it's not very complicated but there are a few corner cases to be carfull with assuming of course you are using standard CSV format.

But why reinventing the wheel...

You can try looking up a CSV parser like

There are others, look around I'm sure you will find one that suits your needs.

Upvotes: 2

Param
Param

Reputation: 2540

If you just want one value out of such a string, you can use String's indexOf() and substring() methods:

String getValue(String str, String key)
{
    int keyIndex = str.indexOf(key + "=");

    if(keyIndex == -1) return null;

    int startIndex = str.indexOf("\"", keyIndex);
    int endIndex = str.indexOf("\"", startIndex);
    String value = str.substring(startIndex + 1, endIndex);
    return value;
}

Upvotes: 0

Java Drinker
Java Drinker

Reputation: 3167

To separate the string by commas, the other posters are correct. It is best to use a CSV parser (your own or OTS). Considering things like commas inside quotes etc can lead to a lot of un-considered problems.

Once you have each separate token in the form:

key = "value"

I think it is easy enough to look for the first index of '='. Then the part before that will be the key, and the part after that will be the value. Then you can store them in a Map<String, String>. This is assuming that your keys will be simple enough, and not contain = in them etc. Sometimes it's enough to take the simple route when you can restrict the problem scope.

Upvotes: 0

sockets-to-me
sockets-to-me

Reputation: 451

Usually you will want to parse the string into a map because you will be pulling various values perhaps multiple times, so it often makes sense to pay the parsing cost up-front.

If not, then here is how I would solve the problem (assuming you want to differentiate between int values and String values).:

public Object pullValue(String pairs, String key) {
    boolean returnString = false;
    int keyStart = pairs.indexOf(key + "=");
    if (keyStart < 0) {
        logger.error("Key " + key + " not found in key-value pairs string");
        return null;
    }
    int valueStart = keyStart + key.length() + 1;
    if (pairs.charAt(valueStart) == '"') {
        returnString = true;
        valueStart++;    // Skip past the quote mark
    }
    int valueEnd;
    if (returnString) {
        valueEnd = pairs.indexOf('"', valueStart);
        if (valueEnd < 0) {
            logger.error("Unmatched double quote mark extracting value for key " + key)
        }
        return pairs.substring(valueStart, valueEnd);
    } else {
        valueEnd = pairs.indexOf(',', valueStart);
        if (valueEnd < 0) {  // If this is the last key value pair in string
            valueEnd = pairs.length();
        }
        return Integer.decode(pairs.substring(valueStart, valueEnd));
    }

}

Note that this solution assumes no spaces between the key, the equals sign, and the value. If these are possible you will have to create some code to travel the string between them.

Another solution is to use a regular expression parser. You could do something like (this is untested):

Pattern lookingForString = Pattern.compile(key + "[ \t]*=[ \t]*[\"]([^\"]+)[\"]");
Pattern lookingForInt = Pattern.compile(key + "[ \t]*=[ \t]*([^,]+)");
Matcher stringFinder = lookingForString.matcher(pairs);
Matcher intFinder = lookingForInt.matcher(pairs);
if (stringFinder.find()) {
    return stringFinder.group(1);
} else if (intFinder.find()) {
    return Integer.decode(intFinder.group(1));
} else {
    logger.error("Could not extract value for key " + key);
    return null;
}

HTH

Upvotes: 0

Use String.split(yourdata, ',') and you will get a String[]. Then, perform String.split(String[i],"="), on each entry to separate property and values.

Ideally, you should move this data in a Properties object instance. You can then save/load it from XML easily. It has useful methods.

REM: I am assuming that you are savvy enough to understand that this solution won't work if values contain the separator (i.e., the comma) in them...

Upvotes: -1

Related Questions