Is there a convenient way to convert comma separated string to hashmap - java

String format is (not json format):
a="0PN5J17HBGZHT7JJ3X82", b="frJIUN8DYpKDtOLCwo/yzg="
I want convert this string to a HashMap:
key a with value 0PN5J17HBGZHT7JJ3X82
key b with value frJIUN8DYpKDtOLCwo/yzg=
Is there a convenient way? Thanks
What I've tried:
Map<String, String> map = new HashMap<String, String>();
String s = "a=\"00PN5J17HBGZHT7JJ3X82\",b=\"frJIUN8DYpKDtOLCwo/yzg=\"";
String []tmp = StringUtils.split(s,',');
for (String v : tmp) {
String[] t = StringUtils.split(v,'=');
map.put(t[0], t[1]);
}
I get this result:
key a with value "0PN5J17HBGZHT7JJ3X82"
key b with value "frJIUN8DYpKDtOLCwo/yzg
for key a, the start and end double quotation marks(") is unwanted; for key b, the start double quotation marks(") is unwanted and the last equals sign(=) is missing.
Sorry for my poor english.

Probably you don't care that it's a HashMap, just a Map, so this will do it, since Properties implements Map:
import java.io.StringReader;
import java.util.*;
public class Strings {
public static void main(String[] args) throws Exception {
String input = "a=\"0PN5J17HBGZHT7JJ3X82\", b=\"frJIUN8DYpKDtOLCwo/yzg=\"";
String propertiesFormat = input.replaceAll(",", "\n");
Properties properties = new Properties();
properties.load(new StringReader(propertiesFormat));
System.out.println(properties);
}
}
Output:
{b="frJIUN8DYpKDtOLCwo/yzg=", a="0PN5J17HBGZHT7JJ3X82"}
If you absolutely need a HashMap, you can construct one with the Properties object as input: new HashMap(properties).

Added few changes in Ryan's code
public static void main(String[] args) throws Exception {
String input = "a=\"0PN5J17HBGZHT7JJ3X82\", b=\"frJIUN8DYpKDtOLCwo/yzg=\"";
input=input.replaceAll("\"", "");
String propertiesFormat = input.replaceAll(",", "\n");
Properties properties = new Properties();
properties.load(new StringReader(propertiesFormat));
Set<Entry<Object, Object>> entrySet = properties.entrySet();
HashMap<String,String > map = new HashMap<String, String>();
for (Iterator<Entry<Object, Object>> it = entrySet.iterator(); it.hasNext();) {
Entry<Object,Object> entry = it.next();
map.put((String)entry.getKey(), (String)entry.getValue());
}
System.out.println(map);
}

Split the String on the Basis of commas (",") and then with with ("=")
String s = "Comma Separated String";
HashMap<String, String> map = new HashMap<String, String>();
String[] arr = s.split(",");
String[] arStr = arr.split("=");
map.put(arr[0], arr[1]);

You can also use the regex as below.
Map<String,String> data = new HashMap<String,String>();
Pattern p = Pattern.compile("[\\{\\}\\=\\, ]++");
String[] split = p.split(text);
for ( int i=0; i+2 <= split.length; i+=2 ){
data.put( split[i], split[i+1] );
}
return data;

Related

Best way to create Object using String variable Android

i have a question using a String with this format in Java(Android):
"{ key1 = value2, key2 = value2 }"
What's the best way to convert this String into Object?
I appreciate any help!
HashMap<String, String> getMap(String rawData) {
HashMap<String, String> map = new HashMap<>();
String[] pairs = rawData.split(","); // split into key-value pairs
for(String pair: pairs) {
pair = pair.trim(); // get rid of extraneous white-space
String[] components = pair.split("=");
String key = components[0].trim();
String value = components[1].trim();
map.put(key, value); // put the pair into the map
}
return map;
}
You can use it like this:
HashMap<String, String> map = getMap("key1 = value1, key2 = value2");
String valueForKey1 = map.get("key1"); // = value1

How to Loop next element in hashmap

I have a set of strings like this
A_2007-04, A_2007-09, A_Agent, A_Daily, A_Execute, A_Exec, B_Action, B_HealthCheck
I want output as:
Key = A, Value = [2007-04,2007-09,Agent,Execute,Exec]
Key = B, Value = [Action,HealthCheck]
I'm using HashMap to do this
pckg:{A,B}
count:total no of strings
reports:set of strings
Logic I used is nested loop:
for (String l : reports[i]) {
for (String r : pckg) {
String[] g = l.split("_");
if (g[0].equalsIgnoreCase(r)) {
report.add(g[1]);
dirFiles.put(g[0], report);
} else {
break;
}
}
}
I'm getting output as
Key = A, Value = [2007-04,2007-09,Agent,Execute,Exec]
How to get second key?
Can someone suggest logic for this?
Assuming that you use Java 8, it can be done using computeIfAbsent to initialize the List of values when it is a new key as next:
List<String> tokens = Arrays.asList(
"A_2007-04", "A_2007-09", "A_Agent", "A_Daily", "A_Execute",
"A_Exec", "P_Action", "P_HealthCheck"
);
Map<String, List<String>> map = new HashMap<>();
for (String token : tokens) {
String[] g = token.split("_");
map.computeIfAbsent(g[0], key -> new ArrayList<>()).add(g[1]);
}
In terms of raw code this should do what I think you are trying to achieve:
// Create a collection of String any way you like, but for testing
// I've simply split a flat string into an array.
String flatString = "A_2007-04,A_2007-09,A_Agent,A_Daily,A_Execute,A_Exec,"
+ "P_Action,P_HealthCheck";
String[] reports = flatString.split(",");
Map<String, List<String>> mapFromReportKeyToValues = new HashMap<>();
for (String report : reports) {
int underscoreIndex = report.indexOf("_");
String key = report.substring(0, underscoreIndex);
String newValue = report.substring(underscoreIndex + 1);
List<String> existingValues = mapFromReportKeyToValues.get(key);
if (existingValues == null) {
// This key hasn't been seen before, so create a new list
// to contain values which belong under this key.
existingValues = new ArrayList<>();
mapFromReportKeyToValues.put(key, existingValues);
}
existingValues.add(newValue);
}
System.out.println("Generated map:\n" + mapFromReportKeyToValues);
Though I recommend tidying it up and organising it into a method or methods as fits your project code.
Doing this with Map<String, ArrayList<String>> will be another good approach I think:
String reports[] = {"A_2007-04", "A_2007-09", "A_Agent", "A_Daily",
"A_Execute", "A_Exec", "P_Action", "P_HealthCheck"};
Map<String, ArrayList<String>> map = new HashMap<>();
for (String rep : reports) {
String s[] = rep.split("_");
String prefix = s[0], suffix = s[1];
ArrayList<String> list = new ArrayList<>();
if (map.containsKey(prefix)) {
list = map.get(prefix);
}
list.add(suffix);
map.put(prefix, list);
}
// Print
for (Map.Entry<String, ArrayList<String>> entry : map.entrySet()) {
String key = entry.getKey();
ArrayList<String> valueList = entry.getValue();
System.out.println(key + " " + valueList);
}
for (String l : reports[i]) {
String[] g = l.split("_");
for (String r : pckg) {
if (g[0].equalsIgnoreCase(r)) {
report = dirFiles.get(g[0]);
if(report == null){ report = new ArrayList<String>(); } //create new report
report.add(g[1]);
dirFiles.put(g[0], report);
}
}
}
Removed the else part of the if condition. You are using break there which exits the inner loop and you never get to evaluate the keys beyond first key.
Added checking for existing values. As suggested by Orin2005.
Also I have moved the statement String[] g = l.split("_"); outside inner loop so that it doesn't get executed multiple times.

Searching String Array item in a nested HashMap

I have a nested HashMap (outer_map), which has another HashMap inside of it as a value (inner_map), such implemented as
Map<String, HashMap<String, String>> outer_map = new HashMap<String, HashMap<String, String>>();
Map<String, String> inner_map = new HashMap<String, String>();
The figure below illustrates the whole structures of the maps:
To make the long story short, I need to compare and search values inside the outer_map's vlaue (inner_map) by String Array items, and then produce another String Array to add matched items.
If the String Array has there elements which are the same as one of the inner_map's random (for example; value2, value1, and value7) values, how can I search and compare these items to add to another String Array?
The latest code snippet I tried and I couldn't succeed:
if( !( theStringArray.equals("") ) )
{
while( outer_map.keySet().iterator().hasNext() )
{
for( int i=0; i <= theStringArray.length; i++)
{
// outer_map keys are order as 1,2,3,..,8
theStringArray[i] = outer_map.get(String.valueOf(i+1)).get("key1");
...
}
}
}
EDIT: Map generating function
private void parse(String in) throws IOException
{
reader = new JsonReader(new StringReader(in));
...
int nodeCounter = 1;
while(reader.hasNext())
{
...
String nameAsKey1 = "blabla"; // value1
inner_map.put("name", nameAsKey1);
String surnameAsKey2 = "blabla"; // value2
inner_map.put("surname", surnameAsKey2);
...
outer_map.put(String.valueOf(nodeCounter), (HashMap<String, String>) inner_map);
inner_map = new HashMap<String, String>();
nodeCounter++;
}
}
EDIT: I don't know how I can explain the issue more clearly, but may be this will help to understand about it: Map structure
I assume you have an array of String and a Map of map. Now you want to search the value fields of inner map against the array of String and if make a new string array with matching values.
If that is the case the below program will help you..
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class InnerMapSearch {
public static void main(String[] args) {
Map<String, HashMap<String, String>> outer_map = new HashMap<String, HashMap<String, String>>();
Map<String, String> inner_map = new HashMap<String, String>();
String[] searchParams = {"blabla1", "blabla3", "blabla20"};
//Populating the map
int reader = 1;
while (reader < 10) {
String nameAsKey1 = "blabla" + reader; // value1
inner_map.put("name", nameAsKey1);
String surnameAsKey2 = "blabla" + reader; // value2
inner_map.put("surname", surnameAsKey2);
outer_map.put(String.valueOf(reader), (HashMap<String, String>) inner_map);
inner_map = new HashMap<String, String>();
reader++;
}
//Searching
Set<String> searchResults = new HashSet<String>(); // Using set to avoid duplicate
// Iterate over the outer map
for(String key : outer_map.keySet()){
// Iterate through each inner_map value of outer map
for(Entry<String, String> innerEntry : outer_map.get(key).entrySet()){
// Iterate through the list of search params and see if its present in inner_hashmap
for(String searchParam : searchParams){
if(searchParam.equals(innerEntry.getValue())){
// The search parameter is in inner map so adding to result.
searchResults.add(searchParam);
}
}
}
}
// Converting the list to an array.
String[] searchResultsArray = searchResults.toArray(new String[searchResults.size()]);
}
}

How to parse the string into map

have a string like A=B&C=D&E=F, how to parse it into map?
I would use split
String text = "A=B&C=D&E=F";
Map<String, String> map = new LinkedHashMap<String, String>();
for(String keyValue : text.split(" *& *")) {
String[] pairs = keyValue.split(" *= *", 2);
map.put(pairs[0], pairs.length == 1 ? "" : pairs[1]);
}
EDIT allows for padded spaces and a value with an = or no value. e.g.
A = minus- & C=equals= & E==F
just use guava Splitter
String src="A=B&C=D&E=F";
Map map= Splitter.on('&').withKeyValueSeparator('=').split(src);
public class TestMapParser {
#Test
public void testParsing() {
Map<String, String> map = parseMap("A=B&C=D&E=F");
Assert.assertTrue("contains key", map.containsKey("A"));
Assert.assertEquals("contains value", "B", map.get("A"));
Assert.assertTrue("contains key", map.containsKey("C"));
Assert.assertEquals("contains value", "D", map.get("C"));
Assert.assertTrue("contains key", map.containsKey("E"));
Assert.assertEquals("contains value", "F", map.get("E"));
}
private Map<String, String> parseMap(final String input) {
final Map<String, String> map = new HashMap<String, String>();
for (String pair : input.split("&")) {
String[] kv = pair.split("=");
map.put(kv[0], kv[1]);
}
return map;
}
}
String t = A=B&C=D&E=F;
Map map = new HashMap();
String[] pairs = t.split("&");
//TODO 1) Learn generis 2) Use gnerics
for (String pair : pairs)
{
String[] kv = pair.split("=");
map.put(kv[0], kv[1]);
}
Split the string (either using a StringTokenizer or String.split()) on '&'. On each token just split again on '='. Use the first part as the key and the second part as the value.
It can also be done using a regex, but the problem is really simple enough for StringTokenizer.

Named placeholders in string formatting

In Python, when formatting string, I can fill placeholders by name rather than by position, like that:
print "There's an incorrect value '%(value)s' in column # %(column)d" % \
{ 'value': x, 'column': y }
I wonder if that is possible in Java (hopefully, without external libraries)?
StrSubstitutor of jakarta commons lang is a light weight way of doing this provided your values are already formatted correctly.
http://commons.apache.org/proper/commons-lang/javadocs/api-3.1/org/apache/commons/lang3/text/StrSubstitutor.html
Map<String, String> values = new HashMap<String, String>();
values.put("value", x);
values.put("column", y);
StrSubstitutor sub = new StrSubstitutor(values, "%(", ")");
String result = sub.replace("There's an incorrect value '%(value)' in column # %(column)");
The above results in:
"There's an incorrect value '1' in column # 2"
When using Maven you can add this dependency to your pom.xml:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
not quite, but you can use MessageFormat to reference one value multiple times:
MessageFormat.format("There's an incorrect value \"{0}\" in column # {1}", x, y);
The above can be done with String.format() as well, but I find messageFormat syntax cleaner if you need to build complex expressions, plus you dont need to care about the type of the object you are putting into the string
Another example of Apache Common StringSubstitutor for simple named placeholder.
String template = "Welcome to {theWorld}. My name is {myName}.";
Map<String, String> values = new HashMap<>();
values.put("theWorld", "Stackoverflow");
values.put("myName", "Thanos");
String message = StringSubstitutor.replace(template, values, "{", "}");
System.out.println(message);
// Welcome to Stackoverflow. My name is Thanos.
You can use StringTemplate library, it offers what you want and much more.
import org.antlr.stringtemplate.*;
final StringTemplate hello = new StringTemplate("Hello, $name$");
hello.setAttribute("name", "World");
System.out.println(hello.toString());
public static String format(String format, Map<String, Object> values) {
StringBuilder formatter = new StringBuilder(format);
List<Object> valueList = new ArrayList<Object>();
Matcher matcher = Pattern.compile("\\$\\{(\\w+)}").matcher(format);
while (matcher.find()) {
String key = matcher.group(1);
String formatKey = String.format("${%s}", key);
int index = formatter.indexOf(formatKey);
if (index != -1) {
formatter.replace(index, index + formatKey.length(), "%s");
valueList.add(values.get(key));
}
}
return String.format(formatter.toString(), valueList.toArray());
}
Example:
String format = "My name is ${1}. ${0} ${1}.";
Map<String, Object> values = new HashMap<String, Object>();
values.put("0", "James");
values.put("1", "Bond");
System.out.println(format(format, values)); // My name is Bond. James Bond.
Thanks for all your help! Using all your clues, I've written routine to do exactly what I want -- python-like string formatting using dictionary. Since I'm Java newbie, any hints are appreciated.
public static String dictFormat(String format, Hashtable<String, Object> values) {
StringBuilder convFormat = new StringBuilder(format);
Enumeration<String> keys = values.keys();
ArrayList valueList = new ArrayList();
int currentPos = 1;
while (keys.hasMoreElements()) {
String key = keys.nextElement(),
formatKey = "%(" + key + ")",
formatPos = "%" + Integer.toString(currentPos) + "$";
int index = -1;
while ((index = convFormat.indexOf(formatKey, index)) != -1) {
convFormat.replace(index, index + formatKey.length(), formatPos);
index += formatPos.length();
}
valueList.add(values.get(key));
++currentPos;
}
return String.format(convFormat.toString(), valueList.toArray());
}
This is an old thread, but just for the record, you could also use Java 8 style, like this:
public static String replaceParams(Map<String, String> hashMap, String template) {
return hashMap.entrySet().stream().reduce(template, (s, e) -> s.replace("%(" + e.getKey() + ")", e.getValue()),
(s, s2) -> s);
}
Usage:
public static void main(String[] args) {
final HashMap<String, String> hashMap = new HashMap<String, String>() {
{
put("foo", "foo1");
put("bar", "bar1");
put("car", "BMW");
put("truck", "MAN");
}
};
String res = replaceParams(hashMap, "This is '%(foo)' and '%(foo)', but also '%(bar)' '%(bar)' indeed.");
System.out.println(res);
System.out.println(replaceParams(hashMap, "This is '%(car)' and '%(foo)', but also '%(bar)' '%(bar)' indeed."));
System.out.println(replaceParams(hashMap, "This is '%(car)' and '%(truck)', but also '%(foo)' '%(bar)' + '%(truck)' indeed."));
}
The output will be:
This is 'foo1' and 'foo1', but also 'bar1' 'bar1' indeed.
This is 'BMW' and 'foo1', but also 'bar1' 'bar1' indeed.
This is 'BMW' and 'MAN', but also 'foo1' 'bar1' + 'MAN' indeed.
Apache Commons StringSubstitutor can be used. Note that StrSubstitutor is deprecated.
import org.apache.commons.text.StringSubstitutor;
// ...
Map<String, String> values = new HashMap<>();
values.put("animal", "quick brown fox");
values.put("target", "lazy dog");
StringSubstitutor sub = new StringSubstitutor(values);
String result = sub.replace("The ${animal} jumped over the ${target}.");
// "The quick brown fox jumped over the lazy dog."
This class supports providing default values for variables.
String result = sub.replace("The number is ${undefined.property:-42}.");
// "The number is 42."
To use recursive variable replacement, call setEnableSubstitutionInVariables(true);.
Map<String, String> values = new HashMap<>();
values.put("b", "c");
values.put("ac", "Test");
StringSubstitutor sub = new StringSubstitutor(values);
sub.setEnableSubstitutionInVariables(true);
String result = sub.replace("${a${b}}");
// "Test"
I am the author of a small library that does exactly what you want:
Student student = new Student("Andrei", 30, "Male");
String studStr = template("#{id}\tName: #{st.getName}, Age: #{st.getAge}, Gender: #{st.getGender}")
.arg("id", 10)
.arg("st", student)
.format();
System.out.println(studStr);
Or you can chain the arguments:
String result = template("#{x} + #{y} = #{z}")
.args("x", 5, "y", 10, "z", 15)
.format();
System.out.println(result);
// Output: "5 + 10 = 15"
There is nothing built into Java at the moment of writing this. I would suggest writing your own implementation. My preference is for a simple fluent builder interface instead of creating a map and passing it to function -- you end up with a nice contiguous chunk of code, for example:
String result = new TemplatedStringBuilder("My name is {{name}} and I from {{town}}")
.replace("name", "John Doe")
.replace("town", "Sydney")
.finish();
Here is a simple implementation:
class TemplatedStringBuilder {
private final static String TEMPLATE_START_TOKEN = "{{";
private final static String TEMPLATE_CLOSE_TOKEN = "}}";
private final String template;
private final Map<String, String> parameters = new HashMap<>();
public TemplatedStringBuilder(String template) {
if (template == null) throw new NullPointerException();
this.template = template;
}
public TemplatedStringBuilder replace(String key, String value){
parameters.put(key, value);
return this;
}
public String finish(){
StringBuilder result = new StringBuilder();
int startIndex = 0;
while (startIndex < template.length()){
int openIndex = template.indexOf(TEMPLATE_START_TOKEN, startIndex);
if (openIndex < 0){
result.append(template.substring(startIndex));
break;
}
int closeIndex = template.indexOf(TEMPLATE_CLOSE_TOKEN, openIndex);
if(closeIndex < 0){
result.append(template.substring(startIndex));
break;
}
String key = template.substring(openIndex + TEMPLATE_START_TOKEN.length(), closeIndex);
if (!parameters.containsKey(key)) throw new RuntimeException("missing value for key: " + key);
result.append(template.substring(startIndex, openIndex));
result.append(parameters.get(key));
startIndex = closeIndex + TEMPLATE_CLOSE_TOKEN.length();
}
return result.toString();
}
}
Apache Commons Lang's replaceEach method may come in handy dependeding on your specific needs. You can easily use it to replace placeholders by name with this single method call:
StringUtils.replaceEach("There's an incorrect value '%(value)' in column # %(column)",
new String[] { "%(value)", "%(column)" }, new String[] { x, y });
Given some input text, this will replace all occurrences of the placeholders in the first string array with the corresponding values in the second one.
You should have a look at the official ICU4J library. It provides a MessageFormat class similar to the one available with the JDK but this former supports named placeholders.
Unlike other solutions provided on this page. ICU4j is part of the ICU project that is maintained by IBM and regularly updated. In addition, it supports advanced use cases such as pluralization and much more.
Here is a code example:
MessageFormat messageFormat =
new MessageFormat("Publication written by {author}.");
Map<String, String> args = Map.of("author", "John Doe");
System.out.println(messageFormat.format(args));
As of 2022 the up-to-date solution is Apache Commons Text StringSubstitutor
From the doc:
// Build map
Map<String, String> valuesMap = new HashMap<>();
valuesMap.put("animal", "quick brown fox");
valuesMap.put("target", "lazy dog");
String templateString = "The ${animal} jumped over the ${target} ${undefined.number:-1234567890} times.";
// Build StringSubstitutor
StringSubstitutor sub = new StringSubstitutor(valuesMap);
// Replace
String resolvedString = sub.replace(templateString)
;
You could have something like this on a string helper class
/**
* An interpreter for strings with named placeholders.
*
* For example given the string "hello %(myName)" and the map <code>
* <p>Map<String, Object> map = new HashMap<String, Object>();</p>
* <p>map.put("myName", "world");</p>
* </code>
*
* the call {#code format("hello %(myName)", map)} returns "hello world"
*
* It replaces every occurrence of a named placeholder with its given value
* in the map. If there is a named place holder which is not found in the
* map then the string will retain that placeholder. Likewise, if there is
* an entry in the map that does not have its respective placeholder, it is
* ignored.
*
* #param str
* string to format
* #param values
* to replace
* #return formatted string
*/
public static String format(String str, Map<String, Object> values) {
StringBuilder builder = new StringBuilder(str);
for (Entry<String, Object> entry : values.entrySet()) {
int start;
String pattern = "%(" + entry.getKey() + ")";
String value = entry.getValue().toString();
// Replace every occurence of %(key) with value
while ((start = builder.indexOf(pattern)) != -1) {
builder.replace(start, start + pattern.length(), value);
}
}
return builder.toString();
}
Based on the answer I created MapBuilder class:
public class MapBuilder {
public static Map<String, Object> build(Object... data) {
Map<String, Object> result = new LinkedHashMap<>();
if (data.length % 2 != 0) {
throw new IllegalArgumentException("Odd number of arguments");
}
String key = null;
Integer step = -1;
for (Object value : data) {
step++;
switch (step % 2) {
case 0:
if (value == null) {
throw new IllegalArgumentException("Null key value");
}
key = (String) value;
continue;
case 1:
result.put(key, value);
break;
}
}
return result;
}
}
then I created class StringFormat for String formatting:
public final class StringFormat {
public static String format(String format, Object... args) {
Map<String, Object> values = MapBuilder.build(args);
for (Map.Entry<String, Object> entry : values.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
format = format.replace("$" + key, value.toString());
}
return format;
}
}
which you could use like that:
String bookingDate = StringFormat.format("From $startDate to $endDate"),
"$startDate", formattedStartDate,
"$endDate", formattedEndDate
);
I created also a util/helper class (using jdk 8) which can format a string an replaces occurrences of variables.
For this purpose I used the Matchers "appendReplacement" method which does all the substitution and loops only over the affected parts of a format string.
The helper class isn't currently well javadoc documented. I will changes this in the future ;)
Anyway I commented the most important lines (I hope).
public class FormatHelper {
//Prefix and suffix for the enclosing variable name in the format string.
//Replace the default values with any you need.
public static final String DEFAULT_PREFIX = "${";
public static final String DEFAULT_SUFFIX = "}";
//Define dynamic function what happens if a key is not found.
//Replace the defualt exception with any "unchecked" exception type you need or any other behavior.
public static final BiFunction<String, String, String> DEFAULT_NO_KEY_FUNCTION =
(fullMatch, variableName) -> {
throw new RuntimeException(String.format("Key: %s for variable %s not found.",
variableName,
fullMatch));
};
private final Pattern variablePattern;
private final Map<String, String> values;
private final BiFunction<String, String, String> noKeyFunction;
private final String prefix;
private final String suffix;
public FormatHelper(Map<String, String> values) {
this(DEFAULT_NO_KEY_FUNCTION, values);
}
public FormatHelper(
BiFunction<String, String, String> noKeyFunction, Map<String, String> values) {
this(DEFAULT_PREFIX, DEFAULT_SUFFIX, noKeyFunction, values);
}
public FormatHelper(String prefix, String suffix, Map<String, String> values) {
this(prefix, suffix, DEFAULT_NO_KEY_FUNCTION, values);
}
public FormatHelper(
String prefix,
String suffix,
BiFunction<String, String, String> noKeyFunction,
Map<String, String> values) {
this.prefix = prefix;
this.suffix = suffix;
this.values = values;
this.noKeyFunction = noKeyFunction;
//Create the Pattern and quote the prefix and suffix so that the regex don't interpret special chars.
//The variable name is a "\w+" in an extra capture group.
variablePattern = Pattern.compile(Pattern.quote(prefix) + "(\\w+)" + Pattern.quote(suffix));
}
public static String format(CharSequence format, Map<String, String> values) {
return new FormatHelper(values).format(format);
}
public static String format(
CharSequence format,
BiFunction<String, String, String> noKeyFunction,
Map<String, String> values) {
return new FormatHelper(noKeyFunction, values).format(format);
}
public static String format(
String prefix, String suffix, CharSequence format, Map<String, String> values) {
return new FormatHelper(prefix, suffix, values).format(format);
}
public static String format(
String prefix,
String suffix,
BiFunction<String, String, String> noKeyFunction,
CharSequence format,
Map<String, String> values) {
return new FormatHelper(prefix, suffix, noKeyFunction, values).format(format);
}
public String format(CharSequence format) {
//Create matcher based on the init pattern for variable names.
Matcher matcher = variablePattern.matcher(format);
//This buffer will hold all parts of the formatted finished string.
StringBuffer formatBuffer = new StringBuffer();
//loop while the matcher finds another variable (prefix -> name <- suffix) match
while (matcher.find()) {
//The root capture group with the full match e.g ${variableName}
String fullMatch = matcher.group();
//The capture group for the variable name resulting from "(\w+)" e.g. variableName
String variableName = matcher.group(1);
//Get the value in our Map so the Key is the used variable name in our "format" string. The associated value will replace the variable.
//If key is missing (absent) call the noKeyFunction with parameters "fullMatch" and "variableName" else return the value.
String value = values.computeIfAbsent(variableName, key -> noKeyFunction.apply(fullMatch, key));
//Escape the Map value because the "appendReplacement" method interprets the $ and \ as special chars.
String escapedValue = Matcher.quoteReplacement(value);
//The "appendReplacement" method replaces the current "full" match (e.g. ${variableName}) with the value from the "values" Map.
//The replaced part of the "format" string is appended to the StringBuffer "formatBuffer".
matcher.appendReplacement(formatBuffer, escapedValue);
}
//The "appendTail" method appends the last part of the "format" String which has no regex match.
//That means if e.g. our "format" string has no matches the whole untouched "format" string is appended to the StringBuffer "formatBuffer".
//Further more the method return the buffer.
return matcher.appendTail(formatBuffer)
.toString();
}
public String getPrefix() {
return prefix;
}
public String getSuffix() {
return suffix;
}
public Map<String, String> getValues() {
return values;
}
}
You can create a class instance for a specific Map with values (or suffix prefix or noKeyFunction)
like:
Map<String, String> values = new HashMap<>();
values.put("firstName", "Peter");
values.put("lastName", "Parker");
FormatHelper formatHelper = new FormatHelper(values);
formatHelper.format("${firstName} ${lastName} is Spiderman!");
// Result: "Peter Parker is Spiderman!"
// Next format:
formatHelper.format("Does ${firstName} ${lastName} works as photographer?");
//Result: "Does Peter Parker works as photographer?"
Further more you can define what happens if a key in the values Map is missing (works in both ways e.g. wrong variable name in format string or missing key in Map).
The default behavior is an thrown unchecked exception (unchecked because I use the default jdk8 Function which cant handle checked exceptions) like:
Map<String, String> map = new HashMap<>();
map.put("firstName", "Peter");
map.put("lastName", "Parker");
FormatHelper formatHelper = new FormatHelper(map);
formatHelper.format("${missingName} ${lastName} is Spiderman!");
//Result: RuntimeException: Key: missingName for variable ${missingName} not found.
You can define a custom behavior in the constructor call like:
Map<String, String> values = new HashMap<>();
values.put("firstName", "Peter");
values.put("lastName", "Parker");
FormatHelper formatHelper = new FormatHelper(fullMatch, variableName) -> variableName.equals("missingName") ? "John": "SOMETHING_WRONG", values);
formatHelper.format("${missingName} ${lastName} is Spiderman!");
// Result: "John Parker is Spiderman!"
or delegate it back to the default no key behavior:
...
FormatHelper formatHelper = new FormatHelper((fullMatch, variableName) -> variableName.equals("missingName") ? "John" :
FormatHelper.DEFAULT_NO_KEY_FUNCTION.apply(fullMatch,
variableName), map);
...
For better handling there are also static method representations like:
Map<String, String> values = new HashMap<>();
values.put("firstName", "Peter");
values.put("lastName", "Parker");
FormatHelper.format("${firstName} ${lastName} is Spiderman!", map);
// Result: "Peter Parker is Spiderman!"
There is Java Plugin to use string interpolation in Java (like in Kotlin, JavaScript). Supports Java 8, 9, 10, 11…​ https://github.com/antkorwin/better-strings
Using variables in string literals:
int a = 3;
int b = 4;
System.out.println("${a} + ${b} = ${a+b}");
Using expressions:
int a = 3;
int b = 4;
System.out.println("pow = ${a * a}");
System.out.println("flag = ${a > b ? true : false}");
Using functions:
#Test
void functionCall() {
System.out.println("fact(5) = ${factorial(5)}");
}
long factorial(int n) {
long fact = 1;
for (int i = 2; i <= n; i++) {
fact = fact * i;
}
return fact;
}
For more info, please read the project README.
The quick answer is no, unfortunately. However, you can come pretty close to a reasonable syntaks:
"""
You are $compliment!
"""
.replace('$compliment', 'awesome');
It's more readable and predictable than String.format, at least!
My answer is to:
a) use StringBuilder when possible
b) keep (in any form: integer is the best, speciall char like dollar macro etc) position of "placeholder" and then use StringBuilder.insert() (few versions of arguments).
Using external libraries seems overkill and I belive degrade performance significant, when StringBuilder is converted to String internally.
Try Freemarker, templating library.
I tried in just a quick way
public static void main(String[] args)
{
String rowString = "replace the value ${var1} with ${var2}";
Map<String,String> mappedValues = new HashMap<>();
mappedValues.put("var1", "Value 1");
mappedValues.put("var2", "Value 2");
System.out.println(replaceOccurence(rowString, mappedValues));
}
private static String replaceOccurence(String baseStr ,Map<String,String> mappedValues)
{
for(String key :mappedValues.keySet())
{
baseStr = baseStr.replace("${"+key+"}", mappedValues.get(key));
}
return baseStr;
}
I ended up with the next solution:
Create class TemplateSubstitutor with method substitute() and use it to format output
Then create a string template and fill it with values
import java.util.*;
public class MyClass {
public static void main(String args[]) {
String template = "WRR = {WRR}, SRR = {SRR}\n" +
"char_F1 = {char_F1}, word_F1 = {word_F1}\n";
Map<String, Object> values = new HashMap<>();
values.put("WRR", 99.9);
values.put("SRR", 99.8);
values.put("char_F1", 80);
values.put("word_F1", 70);
String message = TemplateSubstitutor.substitute(values, template);
System.out.println(message);
}
}
class TemplateSubstitutor {
public static String substitute(Map<String, Object> map, String input_str) {
String output_str = input_str;
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
output_str = output_str.replace("{" + key + "}", String.valueOf(value));
}
return output_str;
}
}
https://dzone.com/articles/java-string-format-examples String.format(inputString, [listOfParams]) would be the easiest way. Placeholders in string can be defined by order. For more details check the provided link.

Categories