2

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?

villat
  • 65
  • 1
  • 8

2 Answers2

1

...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();
    }
}
Sharon Ben Asher
  • 13,849
  • 5
  • 33
  • 47
0

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());
Community
  • 1
  • 1
GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • and what if the person's name contains two words with a blank in between? what if the pet is a "German Shepherd"? – Sharon Ben Asher Dec 21 '16 at 10:17
  • i am talking about OP saying "I need to read from this text file to fill map" – Sharon Ben Asher Dec 21 '16 at 10:22
  • Ah, now I get it. Updated my answer accordingly. – GhostCat Dec 21 '16 at 10:27
  • it would be more polite to point to my answer instead of wiriting it up as if it is yours.... – Sharon Ben Asher Dec 21 '16 at 10:30
  • I didn't read your answer until now. I was focusing on editing my answer; and as you might have noticed, busy with the other aspect of his question first. And dont get me wrong; so far your answer isn't exactly great. – GhostCat Dec 21 '16 at 10:43
  • well, the whole point of formats like json and xml and their parsers is to work on pojo without requiring the dev to implement helper methods or overriding `toString()` methods, now isn't it? – Sharon Ben Asher Dec 21 '16 at 10:46