node-redis icon indicating copy to clipboard operation
node-redis copied to clipboard

Redis Sentinel ip address/hostname mapping config

Open elimelt opened this issue 1 month ago • 2 comments

Motivation

It would be really helpful if we could map the ip/hostname sentinel responds with to a custom address for testing/proxying, like how redis cluster supports nodeAddressMap

I can open a PR if this is an acceptable feature to add

Basic Code Example

createSentinel({
  ...
  nodeAddressMap: {
    '10.0.0.1:30001': {
      host: 'external-host.io',
      port: 30001
    },
    '10.0.0.2:30002': {
      host: 'external-host.io',
      port: 30002
    }
  }
}

elimelt avatar Dec 05 '25 20:12 elimelt

Hi @pinyht

Thanks a lot for the detailed report and the reproducible code example — that’s really helpful. 🙏 We haven’t had a chance to review it yet, but I’ll try to triage and provide feedback by the end of this week.

ggivo avatar Sep 15 '25 05:09 ggivo

@pinyht

This is a limitation of the current implementation. By default, Jedis uses the Gson library for JSON serialization/deserialization. The JSONObject instances returned by Gson are not serializable, which causes JedisCacheException: Failed to serialize object when used with Client-Side Caching (CSC).

Note:
jsonGet(String key, Path2... paths) is still bound to the default Gson implementation and does not use the configured JsonObjectMapper. Until this is resolved, the only alternative I can come up with is the deprecated jsonGet(String key, Path... paths) method instead.

Workaround: Use Custom JsonObjectMapper

Configure a custom JsonObjectMapper (e.g., Jackson) that returns serializable JSON objects:

Click to expand example code

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import redis.clients.jedis.DefaultJedisClientConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisClientConfig;
import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.csc.CacheConfig;
import redis.clients.jedis.exceptions.JedisException;
import redis.clients.jedis.json.JsonObjectMapper;
import redis.clients.jedis.json.Path;

class CSCJsonSerialisationError {

    public static void main(String[] args) {
        CacheConfig cacheConfig = CacheConfig.builder().build();
        JedisClientConfig clientConfig = DefaultJedisClientConfig.builder().resp3().build();
        HostAndPort hp = new HostAndPort("localhost", 6379);

        // Using Jackson
        System.out.println("======================================================= ");
        System.out.println("JedisPooled created with JsonObjectMapper - Jackson");
        try (JedisPooled jedis = new JedisPooled(hp, clientConfig, cacheConfig);) {
            jedis.setJsonObjectMapper(getCustomJacksonObjectMapper());

            jedis.del("testKey-custom-jackson");
            String resp1 = jedis.jsonSet("testKey-custom-jackson", Path.of("$"), "{\"key\":\"testKey-custom-jackson-value\"}");
            System.out.println(resp1);

            Object resp2 = jedis.jsonGet("testKey-custom-jackson", Path.of("$.key"));
            System.out.println("testKey-custom-jackson.key: " + resp2);
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }
    }

    public static CustomJacksonObjectMapper getCustomJacksonObjectMapper() {
        ObjectMapper om = new ObjectMapper();
        om.registerModule(new JavaTimeModule());
        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return new CustomJacksonObjectMapper(om);
    }

    public static class CustomJacksonObjectMapper implements JsonObjectMapper {

        private final ObjectMapper om;

        CustomJacksonObjectMapper(ObjectMapper om) {
            this.om = om;
        }

        @Override
        public <T> T fromJson(String value, Class<T> valueType) {
            try {
                return om.readValue(value, valueType);
            } catch (JsonProcessingException e) {
                throw new JedisException(e);
            }
        }

        @Override
        public String toJson(Object value) {
            try {
                return om.writeValueAsString(value);
            } catch (JsonProcessingException e) {
                throw new JedisException(e);
            }
        }

    }
}
**Note:** Jedis also provides `JsonObjectMapperTestUtil` in the test utilities package (`redis.clients.jedis.util`) with pre-configured Jackson and Gson mappers that you can reference.

ggivo avatar Nov 25 '25 13:11 ggivo