Use and problems of Jackson2JsonRedisSerializer

1. Use

public static RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        lettuceConnectionFactory.setShareNativeConnection(false);
        RedisTemplate<String, Object> rt = new RedisTemplate<>();
        // 设置连接工厂
        rt.setConnectionFactory(lettuceConnectionFactory);
        // 设置 key 的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        rt.setKeySerializer(stringRedisSerializer);
        rt.setHashKeySerializer(stringRedisSerializer);
        // 创建 JSON 序列化工具
        Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        /*ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jsonRedisSerializer.setObjectMapper(mapper);*/
        // 设置 value 的序列化
        rt.setValueSerializer(jsonRedisSerializer);
        rt.setHashValueSerializer(jsonRedisSerializer);
        rt.afterPropertiesSet();
        return rt;
    }

2. Scene simulation

(1) Do not set ObjectMapper to directly initiate set and get a JSON object operation result

SET code:

    @Test
    void redisSetTest() throws Exception {
        String jsonStr = "{\"packetId\":5,\"packetHash\":\"a828f89bfde8639d2caab1ae9b1d953f34b407af1597cce9343620b99f995334\",\"witnessNum\":100,\"remainWitnessNum\":97,\"blockNumber\":5,\"blockHash\":\"35354e07c9fb895c1c02851ca1e55c44fda87c21d87c3d47c8ffc20c8206e2a9\",\"blockTimestamp\":1682385969}";
        redisService.set("test1", JSON.parseObject(jsonStr));
    }

After execution, redis saves the result:

get code:

    @Test
    void redisGetTest() throws Exception {
        Object test1 = redisService.get("test1");
        JSONObject jsonObject = JSON.parseObject((String) test1);
        System.out.println(jsonObject);
    }

Results of the:

java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to java.lang.String

 What the set enters is a JSONOBJECT, and then when the get comes out, it becomes a LinkedHashMap. Look at the source code:

Jackson2JsonRedisSerializer--》deserialize--》this.objectMapper.readValue--》
result = ctxt.readRootValue(p, valueType, this._findRootDeserializer(ctxt, valueType), (Object)null);

this._findRootDeserializer(ctxt, valueType) This code finally returns:

UntypedObjectDeserializer

Then deserialization depends on this class

deserialize(JsonParser p, DeserializationContext ctxt)

method, there are many LinkedHashMaps found in the mapObject source code, which is the reason for the final deserialized object.

if (key1 == null) {
            return new LinkedHashMap(2);
        } else {
            p.nextToken();
            Object value1 = this.deserialize(p, ctxt);
            String key2 = p.nextFieldName();
            if (key2 == null) {
                LinkedHashMap<String, Object> result = new LinkedHashMap(2);
                result.put(key1, value1);
                return result;
            } else {
                p.nextToken();
                Object value2 = this.deserialize(p, ctxt);
                String key = p.nextFieldName();
                LinkedHashMap result;
                if (key == null) {
                    result = new LinkedHashMap(4);
                    result.put(key1, value1);
                    return result.put(key2, value2) != null ? this._mapObjectWithDups(p, ctxt, result, key1, value1, value2, key) : result;

 

 (2) Set ObjectMapper to initiate set and get a JSON object operation result

Let go of the ObjectMapper code commented out in "Use", and then execute set:

    @Test
    void redisSetTest() throws Exception {
        String jsonStr = "{\"packetId\":5,\"packetHash\":\"a828f89bfde8639d2caab1ae9b1d953f34b407af1597cce9343620b99f995334\",\"witnessNum\":100,\"remainWitnessNum\":97,\"blockNumber\":5,\"blockHash\":\"35354e07c9fb895c1c02851ca1e55c44fda87c21d87c3d47c8ffc20c8206e2a9\",\"blockTimestamp\":1682385969}";
        redisService.set("test", JSON.parseObject(jsonStr));
    }

After execution, redis saves the result:

 

Execute the get method:

    @Test
    void redisGetTest() throws Exception {
        Object test1 = redisService.get("test");
        JSONObject jsonObject = JSON.parseObject((String) test1);
        System.out.println(jsonObject);
    }

 Return result, error: java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to java.lang.String

This time it is because the type of the object is also placed in the VALUE when saving redis, so when returning, the object is encapsulated according to @class, so we can directly force it to the specified class at this time. Modify the get method as follows:

    @Test
    void redisGetTest() throws Exception {
        Object test1 = redisService.get("test");
        System.out.println( (JSONObject) test1);
    }

The key:test obtained here has @class settings, but the key:test1 we set before when we did not set ObjectMapper does not have @class in it. At this time, an error will be reported when obtaining the value of test1:

org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Could not resolve subtype of [simple type, class java.lang.Object]: missing type id property '@class'

It means that the @class attribute is missing in VALUE, so he doesn't know how to deal with it when deserializing. Due to space, I will not turn over the source code. Compared with the previous code, it is mainly to set the properties of ObjectMapper later, and whether this @class sets the root "JsonTypeInfo.As.PROPERTY" is related. If you remove this, will you be able to get it? The answer is no, and the GET return information is as follows:

org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Unexpected token (START_OBJECT), expected START_ARRAY: need JSON Array to contain As.WRAPPER_ARRAY type information for class java.lang.Object

This means that the format of the return value is incorrect. The expected number is an array, but the result returns a single OBJECT. Why is it expected to be an array? To be simple, after removing the "JsonTypeInfo.As.PROPERTY" in the previous step, adjust the set method to set a new value. Observe VALUE changes, as follows:

 I found that although there is no @class now, it is an array to store an OBJECT result in redis, and put the type in the first element of the array. No way, continue to upload the source code and look at the ObjectMapper source code:

    public ObjectMapper activateDefaultTyping(PolymorphicTypeValidator ptv) {
        return this.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
    }

    public ObjectMapper activateDefaultTyping(PolymorphicTypeValidator ptv, ObjectMapper.DefaultTyping applicability) {
        return this.activateDefaultTyping(ptv, applicability, As.WRAPPER_ARRAY);
    }

    public ObjectMapper activateDefaultTyping(PolymorphicTypeValidator ptv, ObjectMapper.DefaultTyping applicability, As includeAs) {
        if (includeAs == As.EXTERNAL_PROPERTY) {
            throw new IllegalArgumentException("Cannot use includeAs of " + includeAs);
        } else {
            TypeResolverBuilder<?> typer = this._constructDefaultTypeResolverBuilder(applicability, ptv);
            typer = typer.init(Id.CLASS, (TypeIdResolver)null);
            typer = typer.inclusion(includeAs);
            return this.setDefaultTyping(typer);
        }
    }

Remove the "JsonTypeInfo.As.PROPERTY" mentioned above, and then call the ObjectMapper construction method corresponding to the first two, we can see As.WRAPPER_ARRAY, which roughly knows that it needs to be an ARRAY from the literal meaning, which is actually true, and then Introduce this specific role. For one of the above test results, although removing "JsonTypeInfo.As.PROPERTY" can solve the problem that the obtained VALUE value does not have @class, but the VALUE value needs to be in an array format.

Introduction to JsonTypeInfo.As

  • PROPERTY Include the object type in object member properties
  • WRAPPER_OBJECT takes object types as keys and serialized objects as values
  • The first element of WRAPPER_ARRAY is the object type, and the second element is the serialized object

The rendering effects of using different categories of VALUE are as follows:

  // Type name as a property, same as above
  {
    "@type" : "Employee",
     ...
  }
 
  // Wrapping class name as pseudo-property (similar to JAXB)
  {
    "com.fasterxml.beans.EmployeeImpl" : {
       ... // actual instance data without any metadata properties
    }
  }
 
  // Wrapping class name as first element of wrapper array:
  [
    "com.fasterxml.beans.EmployeeImpl",
    {
       ... // actual instance data without any metadata properties
    }
  ]

At this point, you should probably understand why the VALUE without the object type cannot be resolved after removing "JsonTypeInfo.As.PROPERTY". (Those who see this, I admire your persistence). Since mapper.activateDefaultTyping requires the object type no matter how it is set, what should we do if we still want to get the value without object information at this time (many people may have no idea about this, if you can’t get it, you can use it uniformly Let’s talk about the method of object information, toss this ghost thing. ^_^, in fact, when you use redis to communicate data across languages, you will find that you still have to toss, O(∩_∩)O haha~, for example, the GO language is responsible for SET data, but GO definitely doesn’t know the object type of JAVA, so he can only give you an OBJECT without object information, and if the JAVA side doesn’t do anything at this time, then it can only be scolding)

Cross-language VALUE parsing method

As mentioned above, adding GO does not know the JAVA object information, and SET does not put the object information. How should the JAVA side get the data at this time. The easiest way is to remove the ObjectMapper configuration. At this time, all classes are uniformly processed by LinkedHashMap. When the data received by JAVA terminal GET is an object, LinkedHashMap is used to receive it, and then converted into its own class.

Another method is how to configure ObjectMapper on the JAVA side or how to configure it, because ObjectMapper is convenient to use on the JAVA side, and the type is put in VALUE and directly forced to transfer it. Let the GO side put the string directly when SET, and use the string GET on the JAVA side.

 

Guess you like

Origin blog.csdn.net/h363659487/article/details/130580340