0

I declared a hashmap of key value pairs as,

HashMap<String, String> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");

I am using a method to serialize this hashmap and convert it into a string using the following code:

public static String serializeMetadata(HashMap<String, String> metadata) throws IOException {
    try(ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
        ObjectOutputStream oos = new ObjectOutputStream(baos)) {
        oos.writeObject(metadata);
        return baos.toString();
    }
}

When I call serializeMetadata method and pass my hashmap as input, I expect map state to be streamed by ObjectOutputStream(oos) and store array of bytes in ByteArrayOutputStream(baos). when a toString() is called with baos, I expect an output of

"{key1=value1, key2=value2, key3=value3}"

Just the same kind of output when we convert a hashmap to string. Instead, I get some not readable formatted string like,

���sr�java.util.HashMap���`��F� loadFactorI� thresholdxp?@�����������t�key1t�value1t�key2t�value2t�key3t�value3x

I understand, if I deserialize the baos, I get a hashmap again and if I do a toString(), I get the format I need.

I need a formatted string when serialized hashmap is converted to string, so, 1. I can write a unit test and assert hashmap for a string 2. Persist that string(that has all formatted keys and values) in DB for future data analysis. 3. I can call the string from DB in an application and deserialize it back to a hashmap and continue using the existing states.

I don't want a JSON string. Imagine all key value pairs of hashmap are stored in a single field of DB. I could not find the right solution to it, So if there are any duplicate links, please put them in comments. Any help is appreciated. Thanks!

  • 4
    `ObjectOutputStream` uses a binary format to encode objects. Why do you expect it to return something like `"{key1=value1, key2=value2, key3=value3}"`? – Thomas Kläger Jan 09 '19 at 21:30
  • 1
    Possible duplicate of [How to convert hashmap to JSON object in Java](https://stackoverflow.com/questions/12155800/how-to-convert-hashmap-to-json-object-in-java) – Zack Jan 09 '19 at 21:37
  • 1
    There is nothing about json in the question though @Zack, but that is one solution though yes. But easier solution without 3rd party libs are available. – Mattias Isegran Bergander Jan 09 '19 at 21:40
  • The desired output is json: "{key1=value1, key2=value2, key3=value3}" – Zack Jan 09 '19 at 21:47
  • Don't reinvent the wheel, there are well established libraries for this: https://stackoverflow.com/a/40882429/1176178 – Zack Jan 09 '19 at 21:50
  • I am trying to persist a formatted string to the DB instead of a byte stream, so I can store state of list of tasks as a string, so I can manage the tasks state in the future and another class (similar to a scheduler) grabs the string and deserializes it back to a hasmap. – Chaitanya Chaitu Jan 09 '19 at 21:58
  • That's not json though @Zack, check again. It was not a requirement either, just an expectation from what he tried. Probably as that is the default toString() output (there is no fromString though...) – Mattias Isegran Bergander Jan 09 '19 at 22:18
  • @Zack I apologize if I did not convey my question clearly, my intention is to store a formatted readable string separated by a delimiter(like : or ; or ,) and store it in the database as a string. In my future use case, I intend to get the string back from DB and use it in an application(deserialize it back to hashmap) or manage it in another application for data analysis. I don't exactly need a Json string. Imagine in a way, there is a single field for all key, value pairs in DB. – Chaitanya Chaitu Jan 09 '19 at 22:25
  • Have you considered just using `metadata.toString()`? It gives the output you want. – Mark Rotteveel Jan 10 '19 at 16:55
  • Well @mark as stated in previous comment above yes. toString but there is no fromString – Mattias Isegran Bergander Jan 11 '19 at 15:05
  • 1
    @MattiasIsegranBergander And that is why I didn't post it as an answer... – Mark Rotteveel Jan 11 '19 at 16:47

2 Answers2

1

ObjectOutputStream outputs in a binary format and need an ObjectInputStream to read. If you go via String in between as well it will probably not even work.

The easiest solution is probably to use existing classes such as java.util.Properties which inherits from Hashtable (and implements Map as well) and can take all entries from another Map or act as one. Properties can easily be stored to disk via load/store methods (both line based strings key=value and xml format).

To save to a text readable file:

Properties<String, String> props = new Properties<>();
props.putAll(map);
props.store(...);

To read them back again:

Properties<String, String> props = new Properties<>();
props.load(...);

map.putAll(props);
//or just use the props object as your Map as it implements the Map interface, 
//so instead just do this:
Map<String, String> map = props;

https://docs.oracle.com/javase/8/docs/api/java/util/Properties.html

Mattias Isegran Bergander
  • 11,811
  • 2
  • 41
  • 49
  • This is an unnecessary abstraction. Maps can me marshalled and umarshalled directly, no need for this conversion to Properties. – Zack Jan 09 '19 at 21:52
  • 1
    1. Not without extra libraries or custom code. 2.Properties actually implement Map so can be used directly even as a hashmap replacement, nothing extra at all. – Mattias Isegran Bergander Jan 09 '19 at 22:08
  • 1
    Extra libraries? That is the core principle of Java -- the re-usability of code. You're _supposed_ to reuse libraries and _not_ resolve problems which have already been solved. – Zack Jan 10 '19 at 14:16
0

First of all, you say that you don't need a JSON string. But based on what you intend to use this for, a JSON string would work just fine.

But here's a quick and dirty suggestion to serialize the values as a delimiter separated string:

  1. Copy the Map to a TreeMap so that the keys are sorted into a consistent order.

  2. Use values() on the TreeMap to get the values as a list.

  3. Serialize the list as a string; e.g. using StringJoiner and your preferred delimiter.

Problems:

  1. If the key sets for the maps are inconsistent, then your rendering will be ambiguous.

  2. If the toString() method for the value types are not appropriate, you may end up with ambiguous renderings; e.g. if your chosen field delimiter also appears in the toString() output.

  3. If your map's key names change over time, you may run into trouble.

  4. You cannot reconstruct the map without knowing what the key names ought to be.

Using JSON avoids all of those issues: Hint! Using a CSV library would solve 1. and 2., if you could get it to encode one row at a time.


Another alternative is to use ObjectOutputStream to serialize your map to a ByteArrayOutputStream, then encode the captured bytes using base64 encoding. The result can be stored in the database and used to reconstruct the HashMap later.

The disadvantage is that the base64 encoding is opaque, and will probably occupy more space that a JSON version.


You said about your current version:

... I understand, if I deserialize the BAOS, I get a hashmap.

That is true. However, if you "decode" the BAOS contents to a String you would be liable to run into problems, depending on the charset you chose. Your "unreadable string" is a typical example of what happens, and those '�' characters probably indicate characters that could not be decoded properly, and won't re-encode.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216