Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to maintain the reverse order of a treemap during deserialization #248

Open
PanagiotisDrakatos opened this issue Nov 15, 2022 · 2 comments

Comments

@PanagiotisDrakatos
Copy link

PanagiotisDrakatos commented Nov 15, 2022

Hello again, i hope you have a wonderful day. I am facing a simple problem where i have a treemap that stores values in reverse order and my problem is that during deserialization the values are not maintained in the same reverse-order sequence. How to fix it?

This is my simple code:

import io.activej.serializer.annotations.Serialize;

import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;

public class MapClass {

    private Map<Double,String> map;

    public MapClass() {
        this.map = new TreeMap<>(Collections.reverseOrder());
    }

    @Serialize
    public Map<Double, String> getMap() {
        return map;
    }

    public void setMap(Map<Double, String> map) {
        this.map = map;
    }

}
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;

public class MapMain {

    public static void main(String[] args) {
        byte[] buffer = new byte[200];
       MapClass a=new MapClass();
       a.getMap().put(10.0,"val1");
       a.getMap().put(13.0,"val2");
        BinarySerializer<MapClass> serializer = SerializerBuilder.create().build(MapClass.class);
        serializer.encode(buffer, 0,a);
        MapClass copy =  serializer.decode(buffer, 0);
        System.out.println(copy.toString());
    }
}

this is a screenshot from debugging I expect 13 to be first.

image

What i 've tried so far to make it work is the following code and its working correct but i don't know if its the right way to do it or there exists a smarter way.

import io.activej.serializer.*;
import lombok.SneakyThrows;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.TreeMap;

public class CustomSerializerMap extends SimpleSerializerDef<TreeMap<Double, String>> {
    private int length;
    @Override
    protected BinarySerializer<TreeMap<Double, String>> createSerializer(int version, CompatibilityLevel compatibilityLevel) {
        return new BinarySerializer<TreeMap<Double, String>>() {
            @SneakyThrows
            @Override
            public void encode(BinaryOutput out, TreeMap<Double, String> item) {
                ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
                ObjectOutputStream outstream = new ObjectOutputStream(byteOut);
                outstream.writeObject(item);
                out.write(byteOut.toByteArray());
            }

            @SneakyThrows
            @Override
            public TreeMap<Double, String> decode(BinaryInput in) throws CorruptedDataException {
                byte[] bytes = new byte[245];
                in.read(bytes);
                ByteArrayInputStream byteIn = new ByteArrayInputStream(bytes);
                ObjectInputStream instream = new ObjectInputStream(byteIn);
                TreeMap<Double, String> treemap = (TreeMap<Double, String>) instream.readObject();
                return treemap;
            }
        };
    }
}

One last thing i want something to avoid the static byte assignment because during the decode the size will not be known, i am talking for these lines of code:

  byte[] bytes = new byte[245];
  in.read(bytes);

Can we avoid to to write custom serializer for treemap what's your suggestion for the most efficient approach?

@eduard-vasinskyi
Copy link
Contributor

Hi, @PanagiotisDrakatos

ActiveJ Serializer does not support serialization of TreeMaps as that would require a serialization of an arbitrary Comparator.

Supported maps for serialization are: generic Map, HashMap, LinkedHashMap, and EnumMap.

You can absolutely write your own serializer (as you did) if that suits your needs.

Alternatively, we would recommend you to serialize your map not as a TreeMap<K, V>, but as a generic Map<K, V> and then transform Map<K, V> into a TreeMap<K, V> inside a setter/constructor.

A resulting code would look something like this:

public class MapClass {
 
  private TreeMap<Double, String> map;
 
  public MapClass() {
     this.map = new TreeMap<>(Collections.reverseOrder());
  }
 
  @Serialize
  public Map<Double, String> getMap() {
     return map;
  }
 
  public void setMap(Map<Double, String> map) {
     this.map = new TreeMap<>(this.map.comparator());
     this.map.putAll(map);
  }
 
  @Override
  public String toString() {
     return "MapClass{map=" + map + '}';
  }
 
  public static void main(String[] args) {
     byte[] buffer = new byte[200];
     MapClass a = new MapClass();
     a.getMap().put(10.0, "val1");
     a.getMap().put(13.0, "val2");
     System.out.println(a);
     BinarySerializer<MapClass> serializer = SerializerBuilder.create()
.build(MapClass.class);
     serializer.encode(buffer, 0, a);
     MapClass copy = serializer.decode(buffer, 0);
     System.out.println(copy.toString());
  }
}

As for the static buffer size in your custom serializer, you can avoid doing that. All you need to do is to serialize a length of the byte array before serializing the array itself. Then, when decoding an array, you read a size of that array and create a buffer of the required size:

@Override
public void encode(BinaryOutput out, TreeMap<Double, String> item) {
    ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
    ObjectOutputStream outstream = new ObjectOutputStream(byteOut);
    outstream.writeObject(item);
        
    byte[] bytes = byteOut.toByteArray();
    out.writeVarInt(bytes.length);
    out.write(bytes);
}
 
@Override
public TreeMap<Double, String> decode(BinaryInput in) throws CorruptedDataException {
     byte[] bytes = new byte[in.readVarInt()];
     in.read(bytes);
     ByteArrayInputStream byteIn = new ByteArrayInputStream(bytes);
     ObjectInputStream instream = new ObjectInputStream(byteIn);
     TreeMap<Double, String> treemap = (TreeMap<Double, String>) instream.readObject();
     return treemap;
}

@PanagiotisDrakatos
Copy link
Author

@eduard-vasinskyi exactly it working perfectly many thnaks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants