Search code examples
hazelcast

How to store a Map in a Hazelcast Map?


I have a class that has a field of type Map. I'm creating a CompactSerializer to read and write this class to a map in Hazelcast (version 5.1). But I can't figure out how to serialize the map field. There are no methods on the CompactReader or CompactWriter that work with Maps.

Here's a simplified version of the class and it's incomplete serializer:

data class User(
    val id: String,
    val authoritiesByOrgId: Map<String, Set<String>>
)
class UserSerializer: CompactSerializer<User> {


    override fun read(reader: CompactReader): User {

        val id = reader.readString("id")
        val authoritiesByOrgId = // What goes here?

        return User(
                id,
                authoritiesByOrgId
        )

    }


    override fun write(writer: CompactWriter, user: User) {

        writer.apply {

            writeString("id", user.id)
            write???("authoritiesByOrgId", user.authoritiesByOrgId) // TODO What goes here?

        }

    }


}

Solution

  • This is a bit hard because of two things. Right now, Compact related APIs do not support

    • Maps
    • Collections inside collections

    However, there are workarounds. One could write a Compact serializer for your class as below:

    class UserSerializer implements CompactSerializer<User> {
    
        @Override
        public User read(CompactReader reader) {
            String id = reader.readString("id");
            String[] keys = reader.readArrayOfString("orgIds");
            SetWrapper[] values = reader.readArrayOfCompact("authorities", SetWrapper.class);
            HashMap<String, Set<String>> authoritiesByOrgId = new HashMap<>(keys.length);
            for (int i = 0; i < keys.length; i++) {
                authoritiesByOrgId.put(keys[i], values[i].getSet());
            }
            return new User(id, authoritiesByOrgId);
        }
    
        @Override
        public void write(CompactWriter writer, User object) {
            // Write id as it is
            writer.writeString("id", object.getId());
    
            // Serialize map as 2 arrays, one for keys, one for values
            Map<String, Set<String>> authoritiesByOrgId = object.getAuthoritiesByOrgId();
            
            // keys
            writer.writeArrayOfString("orgIds",
                    authoritiesByOrgId.keySet().toArray(new String[0]));
    
            // Since arrays of arrays are not supported yet, wrap the array in
            // another type
            writer.writeArrayOfCompact("authorities",
                    authoritiesByOrgId.values().stream()
                            .map(SetWrapper::new)
                            .toArray());
        }
    
        @Override
        public String getTypeName() {
            return "user";
        }
    
        @Override
        public Class<User> getCompactClass() {
            return User.class;
        }
    }
    
    class SetWrapperSerializer implements CompactSerializer<SetWrapper> {
    
        @Override
        public SetWrapper read(CompactReader reader) {
            String[] set = reader.readArrayOfString("set");
            return new SetWrapper(new HashSet<>(Arrays.asList(set)));
        }
    
        @Override
        public void write(CompactWriter writer, SetWrapper object) {
            object.getSet().toArray(new String[0]);
            writer.writeArrayOfString("set", object.getSet().toArray(new String[0]));
        }
    
        @Override
        public String getTypeName() {
            return "set-wrapper";
        }
    
        @Override
        public Class<SetWrapper> getCompactClass() {
            return SetWrapper.class;
        }
    }
    
    class SetWrapper {
        private final Set<String> set;
    
        SetWrapper(Set<String> set) {
            this.set = set;
        }
    
        public Set<String> getSet() {
            return set;
        }
    }
    
    class User {
        private final String id;
        private final Map<String, Set<String>> authoritiesByOrgId;
    
    
        User(String id, Map<String, Set<String>> authoritiesByOrgId) {
            this.id = id;
            this.authoritiesByOrgId = authoritiesByOrgId;
        }
    
        public String getId() {
            return id;
        }
    
        public Map<String, Set<String>> getAuthoritiesByOrgId() {
            return authoritiesByOrgId;
        }
    }
    
    

    So, the core ideas are

    • serialize a map as 2 arrays: keys and values
    • use a wrapper type to be able to serialize "arrays of arrays"