I have an HashMap<Person,List<Pet> that needs to be saved into the textfile.
I wrote this method:
public void writeToNotepad(){
try(PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(textFileName + ".txt")))) {
if (map.size() < 1)
return;
Set<Map.Entry<Person, List<Pet>>> mapEntry = map.entrySet();
for (Map.Entry<Person, List<Pet>> mapEn :
mapEntry) {
Person p = mapEn.getKey();
Iterator<Pet> iter = mapEn.getValue().iterator();
while (iter.hasNext()){
Pet pet = iter.next();
pw.println(p.getName() + " " + p .getAge() + " " + p.getSex()
+ " " + pet.getName()
+ " " + pet.getType()
+ " " + pet.getAge());
}
}
pw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
but it saves map key multiple times(i think it's because of iterator)
here is output:
JOHN 10 MALE Dog 3
AISEC 12 MALE Loo Cat 12
AISEC 12 MALE Kitty Cat 4
As you see, Aisec repeats 2 times.
Sooner i need to read from this text file to fill map.
Is this good way to write to file, or i can use better?
...and what if the name contains two words with a blank in between??
Why do you need to invent your own format for serialization and de-serialization? why not use an industry standard like json, where you can utilize any of many libraries that can do the serialization and de-serialization. Do you need me to show you how?
EDIT:
OK, it turned out that using json is not as straightforward as I initially thought. Don't get me wrong, it is still better than custom format in the sense that it is bug-proof and supports edge cases like the one I described above.
The obstacle with json is that the key to every object and property has to be a String. So when the key is a user defined type (like Person in your case) it didn’t get serialized properly - there is a need for a transitional data structure before the serialization to json can be performed.
So this is what I did: for each entry in your dictionary, I create a map that holds two entries: a "person" entry with the json String representation of the Person object, and a "pets" entry with the json String representation of the list of pets. So the final String to be serialized is actually a List of Maps.
To give you an idea: every map entry looks like this as json:
{
"person":{"name":"AISEC","age":12,"sex":"MALE"},
"pets":[
{"name":"Loo","age":12,"type":"Cat"},
{"name":"Kitty","age":4,"type":"Cat"}
]
}
The deserialization is simply the reverse operation.
I am using Jackson library as json parser and writer
This is the serialization method. it returns a String that can be written to a file:
public String dictToJson(Map<Person, List<Pet>> map) throws IOException
{
ObjectMapper mapper = new ObjectMapper();
// serialized dictionary is list of maps
List<Map<String, String>> jsonDictionary = new ArrayList<>();
for (Map.Entry<Person, List<Pet>> mapEntry : map.entrySet()) {
// each map entry becomes its own map instance with two entries
Map<String, String> jsonEntry = new HashMap<>();
// write person key as "person" with json string represetation of person object
jsonEntry.put("person", mapper.writeValueAsString(mapEntry.getKey()));
// write pets value as "pets" key with json string represetation of pets list
jsonEntry.put("pets", mapper.writeValueAsString(mapEntry.getValue()));
jsonDictionary.add(jsonEntry);
}
return mapper.writeValueAsString(jsonDictionary);
}
The de-serialization method accpets String (whole content of file):
public Map<Person, List<Pet>> jsonToDict(String json) throws IOException
{
Map<Person, List<Pet>> map = new HashMap<>();
ObjectMapper mapper = new ObjectMapper();
// read json String into list of maps
List<Map<String, String>> jsonDictionary =
mapper.readValue(json, new TypeReference<List<Map<String, Object>>>(){});
// each list item is a map with two entries
for (Map<String, String> jsonEntry : jsonDictionary) {
map.put(
mapper.readValue(jsonEntry.get("person"), Person.class),
mapper.readValue(jsonEntry.get("pets"), new TypeReference<List<Pet>>(){}));
}
return map;
}
usage and test method:
public static void main(String[] args)
{
Map<Person, List<Pet>> map ; {
map = new HashMap<>();
map.put(new Person("JOHN", 10, "MALE"),
Arrays.asList(new Pet[]{new Pet("Spot", 3, "Dog")}));
map.put(new Person("AISEC", 12, "MALE"),
Arrays.asList(new Pet[]{new Pet("Loo", 12, "Cat"), new Pet("Kitty", 4, "Cat")}));
}
try {
// serialize map into String
String serializedDict = dictToJson(map);
// write to file ...
System.out.println(serializedDict);
map.clear();
// de-serialize from String to map
map = jsonToDict(serializedDict);
// check if map was built ok
System.out.println(dictToJson(map));
} catch (Exception e) {
e.printStackTrace();
}
}
I overlooked the read+write part.
First some words about that thing: what you are actually looking for is a human readable way of serializing your data. There are various ways to get there: A) you define your own text format B) you use existing technologies, like JSON or XML.
My recommendation: learn how to use libraries that transform your data in JSON strings; then write those; and read them back using a JSON parser.
Further reading on that: writing and parsing
The key part is: no matter what you, you really have to think through what you are doing here. You have to define a reasonable format for your data; and then you have to write the code to create/parse that content.
But then, on the second part of your question; the current output that comes up:
Here; in your inner loop:
pw.println(p.getName() + " " + p .getAge() + " " + p.getSex()
+ " " + pet.getName()
+ " " + pet.getType()
+ " " + pet.getAge());
You are making a println call that prints all that information. Within your inner loop that loops on the entries of the inner list.
And you are really surprised that an inner loop that runs within an outer loop leads to this result?!
So, the answer is: you have to step back. You put code down that will print
Owner NAME AGE pet1 details
Owner NAME AGE pet2 details
and so on. You have to rework that code; and first you have to clarify the desired output format. For example, that could be:
Owner NAME AGE pet1 details | pet 2 details | and so forth
You could get there by creating a helper method like
public String toString(List<Pet> pets) {
Now you simply iterate your pet owners; and for each owner you print down the owner details; and then you use that method for the pets string.
Beyond that: you could look into overriding
public String toString()
on your Person and your Pet class. In other words: dont ask your objects for their details to build a string from that. Instead: tell the object to provide a string representation of itself.
And then doing a full printout would boil down to:
foreach mapEn
System.out.println("Owner: " + mapEn.getKey().toString() + " pets: " + mapEn.getValue().toString());
Related
I am trying to deserialize my hashmap(JSON) to POJO class using Jackson - ObjectMapper . Below is the hashmap :
List<Object> setJSONValues = new ArrayList<Object>(Arrays.asList(requestObj));
List<String> setJSONKeys = apiUtility.readJSONKeys(new File("ABC.csv"));
HashMap<String, Object> requestMap = new HashMap<String, Object>();
if (setJSONKeys.size() == setJSONValues.size()) {
for (int i = 0; i < setJSONKeys.size(); i++) {
requestMap.put(setJSONKeys.get(i), setJSONValues.get(i));
}
}
I want to use this requestMap into my POJO class using object mapper see below :
objectMapper.readValue(objectMapper.writeValueAsString(requestMap), MyRequestDTO.class);
I get below error :
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field
" "apptDateTime"" (class Collector.MyRequestDTO)
Above error is coming because O/P of my objectMapper.writeValueAsString(requestMap) is :
{" \"apptDateTime\"":"\"2019-03-19 10:00:00\"","\"meter\"":"\"8682\""
Adding Hashmap O/P :
for (String s:requestMap.keySet())
System.out.println("Key is "+s+"Value is "+requestMap.get(s));
Output : Key is "apptDateTime"Value is "2019-03-19 10:00:00" Key is
"meter"Value is "8682"
Your utility method for reading the keys does not work as you expect (this one:)
List<String> setJSONKeys = apiUtility.readJSONKeys(new File("ABC.csv"));
It is returning keys and values wrapped in double quotes, so a key that is supposed to be "apptDateTime" is actually returned as " \"apptDateTime\"". You can see this in the debug output you added: you don't add quotes around the keys or the values, but the output shows quotes anyway.
You can work around the bug by removing the wrapping quotes as follows, but it would be better to fix the function that returns unexpected data in the first place.
String key = removeQuotes(setJSONKeys.get(i));
String value = removeQuotes(setJSONValues.get(i))
requestMap.put(key, setJSONValues.get(i));
...
String removeQuotes(String key) {
key = key.trim();
key = key.substring(1, key.length() - 1); // remove quotes
return key.trim();
}
I've created a hash map that groups unique keys that combine three parameters, i.e. customer, sc and admin. I want to create a unique list of keys with a list of servers attached. I've implemented the following:
public static void main(String[] args) {
String items = "customer1^sc1^admin1|server1~" +
"customer1^sc1^admin1|server2~" +
"customer1^sc1^admin1|server3~" +
"customer2^sc1^admin1|server1~" +
"customer3^sc1^admin1|server3~" +
"customer3^sc1^admin1|server2~";
// Set up raw data
List<String> splitItems = Arrays.asList(items.split("\\s*~\\s*"));
// Display raw data
System.out.println("Raw List: " + items);
// Create a hash map containing customer name as key and list of logs as value
HashMap<String, List<String>> customerHashMap = new HashMap<>();
// Loop through raw data
for (String item : splitItems) {
// Create new lists. One for customers and one for logs
// List<String> customerList = new ArrayList<>();
List<String> logList;
String list[] = item.split("\\|");
String customer = list[0];
String log = list[1];
logList = customerHashMap.get(customer);
if (logList == null){
logList = new ArrayList<>();
customerHashMap.put(customer, logList);
}
logList.add(log);
// System.out.println(logList);
}
// Print out of the final hash map. Customer "a" should only have "a" logs, customer "b" with "b", etc.
System.out.println("");
List<String> hashMapList = new ArrayList<String>();
Iterator it = customerHashMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
String output = pair.getKey() + "|" + pair.getValue().toString();
hashMapList.add(output);
it.remove();
}
String hashMapResultString = hashMapList.toString();
String hashMapResultFormatted = hashMapResultString.replaceAll("[\\[\\]]", "");
System.out.println(hashMapResultFormatted);
}
Raw List: customer1^sc1^admin1|server1~customer1^sc1^admin1|server2~customer1^sc1^admin1|server3~customer2^sc1^admin1|server1~customer3^sc1^admin1|server3~customer3^sc1^admin1|server2~
Hash Map String:
customer2^sc1^admin1|server1, customer3^sc1^admin1|server3, server2, customer1^sc1^admin1|server1, server2, server3
I now want to use the hash map to create a string which will be parsed further (don't ask lol). So I set the keys and values of the hash map to a string which separates them with a unique delimiter |. The problem is that because the key is a List<String>, when printing I can't ascertain the beginning of every new key if its value is a list with more than one item, i.e. customer3^sc1^admin1|server3, server2, is followed immediately by customer1^sc1^admin1|server1, server2, server3. I need a delimiter here that separates them.
My ideal output would look like this:
customer2^sc1^admin1|server1~customer3^sc1^admin1|server3, server2~customer1^sc1^admin1|server1, server2, server3~...
How can I achieve this?
Update:
This is the answer I ultimately found useful for my particular problem:
StringBuilder s = new StringBuilder();
for (Map.Entry<String, List<String>> entry : customerHashMap.entrySet()) {
s.append(entry.getKey() + "|");
List<String> list = entry.getValue();
for (String item : list) {
if (item != list.get(list.size() - 1)) {
s.append(item + "^");
} else {
s.append(item);
}
}
s.append("~");
}
System.out.println(s.toString());
You can iterate through a map's entry set:
StringBuilder s = new StringBuilder();
for(Map.Entry<String,List<String>> entry : map.entrySet()) {
s.append(entry.getKey() + "\n");
List<String> list = entry.getValue();
for(String item : list) {
s.append(" " + item + "\n");
}
}
return s.toString();
For the sake of a clearer example, I've output a different format from the one you asked for, but this illustrates how to work with a map of list values. When adapting to your needs, have a look at java.util.StringJoiner and the related Collectors.joining(); it may well be useful.
Streams can be handy here:
String encoded = map.entrySet().stream()
.map( entry -> entry.getValue().stream()
.collect(Collectors.joining("^"))
+ "|" + entry.getKey())
.collect(Collectors.joining("~"));
What happens here is:
We get a stream of Entry<String,List<String> out of the map
The lambda entry -> ... converts each entry into a string of the form val1^v2^v3^...^valN|key, i.e. we are mapping a Stream<Entry<>> into a Stream<String>.
the final collect() joins the stream of strings into a single string using ~ as a delimiter.
I have a ArrayList<Metadata> and i want to know if there is a Java API for working with CSV files which has a write method which accepts a ArrayList<> as parameter similar to LinqToCsv in .Net. As i know OpenCSV is available but the CsvWriter class doesn't accept a collection.
My Metadata Class is
public class Metadata{
private String page;
private String document;
private String loan;
private String type;
}
ArrayList<Metadata> record = new ArrayList<Metadata>();
once i populate the record, i want to write each row into a csv file.
Please suggest.
Surely there'll be a heap of APIs that will do this for you, but why not do it yourself for such a simple case? It will save you a dependency, which is a good thing for any project of any size.
Create a toCsvRow() method in Metadata that joins the strings separated by a comma.
public String toCsvRow() {
return Stream.of(page, document, loan, type)
.map(value -> value.replaceAll("\"", "\"\""))
.map(value -> Stream.of("\"", ",").anyMatch(value::contains) ? "\"" + value + "\"" : value)
.collect(Collectors.joining(","));
}
Collect the result of this method for every Metadata object separated by a new line.
String recordAsCsv = record.stream()
.map(Metadata::toCsvRow)
.collect(Collectors.joining(System.getProperty("line.separator")));
EDIT
Should you not be so fortunate as to have Java 8 and the Stream API at your disposal, this would be almost as simple using a traditional List.
public String toCsvRow() {
String csvRow = "";
for (String value : Arrays.asList(page, document, loan, type)) {
String processed = value;
if (value.contains("\"") || value.contains(",")) {
processed = "\"" + value.replaceAll("\"", "\"\"") + "\"";
}
csvRow += "," + processed;
}
return csvRow.substring(1);
}
By using CSVWriter, you could convert the ArrayList to an array, and pass that to the writer .
csvWriter.writeNext(record.toArray(new String[record.size()]));
If you have an ArrayList of Objects (Metadata in your case) you would use the BeanToCSV instead of the CSVWriter.
You can look at the BeanToCSVTest in the opencsv source code for examples of how to use it.
In Python we can do this easily:
data = {'name':'Felix'}
s = 'Hello, %(name)s' % data
s
'Hello, Felix'
Is there a similar way in Java to implement the same thing?
PS:
Sorry for the unclear question. the use case is : we have a map which stores the key-values, the Template only need to specify a key in the map, then the value of the key will be in the place where the key is in the template.
AFAIK you can use String#format for this:
String name = "Felix";
String s = String.format("Hello, %s", name);
System.out.println(s);
This will print
Hello, Felix
More info about how to use the formatting of String#format can be found on java.util.Formatter syntax
You want String.format method.
String data = "Hello, %s";
String updated = String.format(data, "Felix");
If you want to replace only Strings with Strings then code from second part of my answer will be better
Java Formatter class doesn't support %(key)s form, but instead you can use %index$s where index is counted from 1 like in this example
System.out.format("%3$s, %2$s, %1s", "a", "b", "c");
// indexes 1 2 3
output:
c, b, a
So all you need to do is create some array that will contain values used in pattern and change key names to its corresponding indexes (increased by 1 since first index used by Formatter is written as 1$ not as 0$ like we would expect for arrays indexes).
Here is example of method that will do it for you
// I made this Pattern static and put it outside of method to compile it only once,
// also it will match every (xxx) that has % before it, but wont include %
static Pattern formatPattern = Pattern.compile("(?<=%)\\(([^)]+)\\)");
public static String format(String pattern, Map<String, ?> map) {
StringBuffer sb = new StringBuffer();
List<Object> valuesList = new ArrayList<>();
Matcher m = formatPattern.matcher(pattern);
while (m.find()) {
String key = m.group(1);//group 1 contains part inside parenthesis
Object value = map.get(key);
// If map doesn't contain key, value will be null.
// If you want to react somehow to null value like throw some
// Exception
// now is the good time.
if (valuesList.contains(value)) {
m.appendReplacement(sb, (valuesList.indexOf(value) + 1) + "\\$");
} else {
valuesList.add(value);
m.appendReplacement(sb, valuesList.size() + "\\$");
}
}
m.appendTail(sb);
return String.format(sb.toString(), valuesList.toArray());
}
usage
Map<String, Object> map = new HashMap<>();
map.put("name", "Felix");
map.put("age", 70);
String myPattern =
"Hi %(emptyKey)s! My name is %(name)s %(name)s and I am %(age)s years old";
System.out.println(format(myPattern, map));
output:
Hi null! My name is Felix Felix and I am 70 years old
As you can see you can use same key few times (in our case name) and if your map wont contain key used in your String pattern (like emptyKey) it will be replaced with null.
Above version was meant to let you set type of data like s d and so on, but if your data will always be replaced with Strings, then you can skip String.format(sb.toString(), valuesList.toArray()) and replace all your keys with values earlier.
Here is simpler version that will accept only map with <String,String> key-value relationship.
static Pattern stringsPattern = Pattern.compile("%\\(([^)]+)\\)s\\b");
public static String formatStrings(String pattern, Map<String, String> map) {
StringBuffer sb = new StringBuffer();
Matcher m = stringsPattern.matcher(pattern);
while (m.find()) {
// we can't use null as replacement so we need to convert it to String
// first. We can do it with String.valueOf method
m.appendReplacement(sb, String.valueOf(map.get(m.group(1))));
}
m.appendTail(sb);
return sb.toString();
}
Under this use case, you need a template engine like velocity or freemarker to use a Map-like data structure to render a string template, there is no builtin module in java to do that. like this(with velocity):
public static void main(String[] args) {
Context context = new VelocityContext();
context.put("appid", "9876543d1");
context.put("ds", "2013-09-11");
StringWriter sw = new StringWriter();
String template = "APPID is ${appid} and DS is ${ds}";
Velocity.evaluate(context, sw, "velocity", template);
System.out.println(sw.toString());
}
If you want more advanced techniques like i18n support, you can use the advanced Message Format features
ex:
in langage properties files you add the property 'template' wich is your message
template = At {2,time,short} on {2,date,long}, \
we detected {1,number,integer} spaceships on \
the planet {0}.
then you can format your valriables pass the arguments in an array:
Object[] messageArguments = {
"Mars",
new Integer(7),
new Date()
};
You call the formatter it this way:
MessageFormat formatter = new MessageFormat("");
formatter.setLocale(currentLocale);
formatter.applyPattern(messages.getString("template"));
String output = formatter.format(messageArguments);
the detailed example is here
http://docs.oracle.com/javase/tutorial/i18n/format/messageFormat.html
Is there a way to convert my output from ToStringBuilder back to java object?
I am looking for an easy way to represent a Java object in readable text file and being able to convert back and forth between string and object.
Thanks,
You must define a strict format and follow it with a parser. There are two accepted formats:
XML - you can use java.beans.XMLEncoder
JSON - use Jackson or gson
If you don't choose these formats you will have to handle the parsing yourself.
The ToStringBuilder does not seem to have a reverse equivalent. Furthermore it is wrong to use this string representation for such purposes - it is meant only for debug.
You'll have to parse your string representation of the object and then construct a new object initialised with those values.
If you want to keep it generic and have it work for any object type, you can use Apache BeanUtils to help.
For example, if your string representation is:
Person#7f54[name=Stephen,age=29,smoker=false]
Parse out the class name, fields and values. Then use BeanUtils to construct a new Person:
String className = "Person";
Class beanClass = Class.forName(className);
Person myPerson = (Person)beanClass.newInstance();
BeanUtils.setProperty(myPerson, "name", "Stephen");
BeanUtils.setProperty(myPerson, "age", "29");
BeanUtils.setProperty(myPerson, "smoker", "false");
This assumes that your Person class is a bean and exposes getters/setters for its fields.
Sean, I came across your question while looking for a simple test case converter based on a reflection-based String output of an object. While a more robust library for json or XML is certainly important for a wide range of input, this is handy for quick and dirty test cases. It handles simple, non-nested POJOs. Its only dependency is apache commons.
I'm using this toString format:
#Override
public String toString() {
return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
Here is the class:
public class FromStringBuilder {
/**
* Parses a string formatted with toStringBuilder
*
* #param input - ex. "Path[id=1039916,displayName=School Home,description=<null>,...]"
* #return hashmap of name value pairs - ex. id=1039916,...
*/
public static Map<String, String> stringToMap(String input) {
LinkedHashMap<String, String> ret = new LinkedHashMap<String, String>();
String partsString = StringUtils.substringBetween(input, "[", "]");
String[] parts = partsString.split(",");
for (String part:parts) {
String[] nv = part.split("=");
if (!StringUtils.equals("<null>", nv[1])) {
ret.put(nv[0], nv[1]);
}
}
return ret;
}
public static <T> T stringToObject(String input, Class<T> clazz) throws IllegalAccessException, InvocationTargetException, InstantiationException {
Map<String, String> map = stringToMap(input);
T ret = clazz.newInstance();
BeanUtils.copyProperties(ret, map);
return ret;
}
}
XStream library is the perfect one
Here is an example of using Gson:
public class GsonTest {
static class Name { String first; String last; }
static class Data { int number; Name name = new Name(); }
public static void main(String[] args) {
Gson gson = new Gson();
Data data1 = new Data();
data1.number = 1;
data1.name.first = "Joe";
data1.name.last = "Smith";
print("data1", data1);
String jsonString = gson.toJson(data1);
System.out.println("jsonString: " + jsonString);
Data data2 = gson.fromJson(jsonString, Data.class);
print("data2", data2);
}
private static void print(String id, Data data) {
System.out.println(id + " :"
+ " number=" + data.number
+ " name.first=" + data.name.first
+ " name.last=" + data.name.last);
}
}
Output
data1 : number=1 name.first=Joe name.last=Smith
jsonString: {"number":1,"name":{"first":"Joe","last":"Smith"}}
data2 : number=1 name.first=Joe name.last=Smith
Speed
Gson should be roughly as fast as any other comparable reflection-based Object<->Text serialization framework, but I do not have benchmark data.
Bean Support
Unlike XStream(optionally) and java.beans.XMLEncoder/XMLEncoder, Gson does not use a class's setter and getter methods. Rather it reads and writes the member fields of the class directly, similar to Java binary serialization (ObjectOutputStream, etc.) While Gson should be able to properly marshal and unmarshal most JavaBeans, this implementation detail of Gson must be kept in mind and accounted for.
Pretty-printed JSON Output
By default GSON outputs JSON all on one line, as an optimization. It can output JSON that is slightly easier to read. Do this by constructing the Gson object in a slightly different way:
Gson gson = new GsonBuilder().setPrettyPrinting().create();
The JSON output (continuing the example above), now looks like:
{
"number": 1,
"name": {
"first": "Joe",
"last": "Smith"
}
}
instead of
{"number":1,"name":{"first":"Joe","last":"Smith"}}