Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I searched all over for a query string parser that could handle nested parameter names ala parse_str in PHP. I was unable to find anything, so this is what I've come up with so far.

Assumptions:

client[0][organizations][0][id]

is the same as

client[0].organizations[0].id

Restrictions:

  1. Given that all keys may be non-sequential and non-numeric, does not utilize Lists, but rather pushes everything into a hierarchical Map.
  2. Does not handle any sort of encoding right now.

Implementation

public class Util {
    private static final Pattern PATTERN = Pattern.compile("([^\\[\\]\\.]+)");

    public static Map<String, Object> parse_str(String str) {
        final String pairs[] = StringUtils.split(str, "&");
        final OurMap result = new OurMap();
        if (pairs == null || pairs.length == 0) {
            return result;
        }

        for (String pair : pairs) {
            final String[] tmp = StringUtils.split(pair, "=", 2);
            final String key = tmp[0];
            final String[] path = keyToPath(key);
            final String value = tmp.length > 1 ? tmp[1] : "";

            // Now iterate over the key path until we're done
            OurMap map = result;
            for (int i = 0; i < path.length; i++) {
                final String part = path[i];
                // If no element exists for this part, we can add it straight
                // away
                if (!map.containsKey(part)) {
                    // This is the end of the key path, so just put the value
                    if (i >= path.length - 1) {
                        map.put(part, value);
                    }
                    else {
                        OurMap m = new OurMap();
                        map.put(part, m);
                        map = m;
                    }
                }

                // An element already exists, so we have to check to see what it
                // is, see if we need to do any conversions, etc.
                else {
                    Object current = map.get(part);
                    // If it's already a map, we can continue on down the path
                    if (current instanceof OurMap) {
                        if (i >= path.length - 1) {
                            ((OurMap)current).put(part, value);
                        }
                        else {
                            map = (OurMap)current;
                        }
                    }
                    // If it's a scalar object, we need to convert it to a list
                    // (in map form for weird indices)
                    else {
                        OurMap m = new OurMap();
                        m.add(current);
                        if (i >= path.length) {
                            m.add(value);
                        }
                        m.add(value);
                        map.put(part, m);
                        map = m;
                    }
                }
            }
        }
        return result;
    }

    protected static String[] keyToPath(final String key) {
        final Matcher matcher = PATTERN.matcher(key);
        final List<String> result = new ArrayList<>();
        while (matcher.find()) {
            result.add(matcher.group());
        }
        return result.toArray(new String[result.size()]);
    }

    public static class OurMap extends HashMap<String, Object> {
        public void add(final Object obj) {
            put(String.valueOf(keySet().size()), obj);
        }
    }
}

Tests

firstName=Jane&lastName=Smith&organizations[0][id]=1234&organizations[0].addresses[].street=Wentworth%20Dr

yields the following map representation

{lastName=Smith, organizations={0={id=1234, addresses={street=Wentworth%20Dr}}}, firstName=Jane}
share|improve this question

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.