“This is my first day of the Novembermore Challenge.The final text challenge in 2021”

Hello everyone, I am Yihang!

Here’s the thing! One day after lunch at noon, was starting the afternoon brick moving task, suddenly the group inside lively, because of busy, also did not go to see, after a while, suddenly a partner in the group @ me, went to climb the building to look at everyone’s chat record, the result is found a very interesting Bug; This seems to be a very basic problem, but for those who don’t have a very solid foundation, if they really encounter it, they may be fooled. This guy in the group was fooled all afternoon, and I would like to share it with you here.

The discussion started when one of the boys asked this question:

The interface returns a Map. The key is of type Long. The Map contains data, but no value can be retrieved.

Because the Key of the underlying data type was converted to String when it was returned as Json, some partners quickly proposed to confirm whether the Key was converted to String, but the result was denied. But I’m skeptical of this denial, so I’ll have to check it for myself;

Problem of comb

In order to understand the situation, it is necessary to comb through briefly;

  • The business scenario looks like this:
    1. The A service provides an interface that returns A Map
      ,>
    2. Service B invokes the corresponding interface of service A through the RestTemplate, and the input parameter is A Long
    3. Service B obtains the Object by receiving the Map

      response and using the Long value as the Key
      ,>
  • Problem:Whether this interface design method is reasonable or not is discussed at the end of this articleService B can receive Map<Long, Object> objects normallylog.info("map:{}",map)Can output the corresponding key and Object normally; But bymap.get(sourceId)Take the Object,Sometimes normal, sometimes null is pulled out; This suddenly becomes interesting; Programmers encounter a Bug, as long as it is necessary or baidu to, it is not a Bug, easily won; Only the bug that sometimes appears sometimes is normal, is the most headache, may make you doubt life for a time;

Repetition Bug

In order to clarify this problem, I simulated his business logic as he wrote it, and wrote a simple code to reproduce normal and abnormal conditions:

  • The key can be Long l = 123456789000L. , the code is as follows:

    @slf4j public class Main {public static void Main (String[] args) throws Exception {//A Service data Map<Long,String> mp = new HashMap<>(); Long l = 123456789000L; mp.put(l,"123"); The info (" key: {} ", l); String s1 = json.tojsonString (mp); String s1 = json.tojsonString (mp); Log.info (" JSON text: {}",s1); Map<Long,String> mp2 = json.parseObject (s1, map.class); Log.info (" JSON text converted Map object: {}",mp2); / / by the key values of the info (" by key: {} get value: {} ", l, mp2. Get (l)); }}Copy the code

    The results

  • L = 123456789L; l = 123456789L;

    Public class Main {public static void Main (String[] args) throws Exception {//A Service data Map<Long,String> mp = new HashMap<>(); Long l = 123456789L; mp.put(l,"123"); String s1 = json.tojsonString (mp); String s1 = json.tojsonString (mp); Log.info (" JSON text: {}",s1); Map<Long,String> mp2 = json.parseObject (s1, map.class); Log.info (" JSON text converted Map object: {}",mp2); / / by the key values of the info (" by key: {} get value: {} ", l, mp2. Get (l)); }}Copy the code

    The results

Results analysis

Found no! Two pieces of code, except the key is different, there is no difference in the logic part, no error, and both work correctly, then why the normal part and the other part of the result is null?

Get (l) = mp2.get(l) = mp2.get(l) = mp2

Boy! Abnormal things will be demon;

If the key is long l = 123456789L, mp2 will not get the value. Because mp2 doesn’t have a Long key at all, it has an Integer key, right? When the Key is Long, the value is retrieved normally. When the Key is Integer, the value is retrieved null

Why is it Integer

I saved a Long as a key. When I transferred the Json text to MP2, I used Map<Long,String> to receive the text. It seems that everything is valid.

After all, the core code is only five simple lines, and a little analysis shows that the problem is in that line of code

Map<Long,String> mp2 = JSON.parseObject(s1,Map.class);
Copy the code

The object passed by the conversion is just a map.class; It does not specify what types of keys and values are in the Map; Because of generic erasure, fastJson cannot determine the specific type of the underlying numeric key. Instead, it uses the length to match the most appropriate data type. Since 123456789 can be received using Integer, it is converted to Integer; 123456789000 can only be received through Long, which is converted to Long;

Here is the fastJson source code for number type judgment; To determine what type the current number needs to be converted to:

if (negative) { if (i > this.np + 1) { if (result >= -2147483648L && type ! = 76) { if (type == 83) { return (short)((int)result); } else if (type == 66) { return (byte)((int)result); } else { return (int)result; } } else { return result; } } else { throw new NumberFormatException(this.numberString()); } } else { result = -result; if (result <= 2147483647L && type ! = 76) { if (type == 83) { return (short)((int)result); } else if (type == 66) { return (byte)((int)result); } else { return (int)result; } } else { return result; }}Copy the code

This explains exactly why the bug appears;

How to solve it?

fastJson

If you use fastJson to convert Json text into objects, you can easily specify the type of key and value in the Map

Map<Long,String> mp2 = JSON.parseObject(s1,new TypeReference<Map<Long,String>>(){});
Copy the code

The value can be retrieved even if the key is 123456789

RestTemplate

This article is caused by requesting another service from RestTemplate without specifying a generic object, so it needs to be specified as well.

  • The sample interface

    @RestController @RequestMapping("/a") public class TestController { @GetMapping("/b") public Map<Long, String> b() { Map<Long, String> mp = new HashMap<>(); mp.put(1L,"123"); mp.put(123456789L,"456"); mp.put(123456789000L,"789"); return mp; }}Copy the code
  • RestTemplate request

    @Autowired RestTemplate restTemplate; @Test public void restTemplate() throws Exception { ParameterizedTypeReference<Map<Long, String>> typeRef = new ParameterizedTypeReference<Map<Long, String>>() {}; Map < Long, String > mp = restTemplate. Exchange (" http://127.0.0.1:8080/a/b ", HttpMethod. GET new HttpEntity < > (null). typeRef).getBody(); log.info("mp:{}", mp); Log.info (" get key :{} ",1L,mp.get(1L)); Log.info (" get key :{} :{} ",123456789L,mp.get(123456789L)); Log.info (" get key :{} :{} ",123456789000L,mp.get(123456789000L)); }Copy the code

thinking

At this point, the whole problem is solved!

But there is another point that I have to mention; The kid’s Map as an object of message interaction is very not recommended for use, through the Map, seemingly improves the flexibility, after all, what objects can be thrown in, but it is a lot to the readability of the code, and maintenance of barriers, because I have no way to see what the Map into the data, also do not know when put the data into it; If I’m just a caller and I want to see what you’re returning, I can’t see it clearly just by looking at the interface definition, but I have to go through the detailed code and see what values you’re putting in the Map and what they mean.

These problems may eventually dig themselves into a deep hole

In order to improve the flexibility, readability and extensibility of the interface, abstract interface packet data based on generics is an important method. The Json format of the message is divided into the common part and the business data part, so that the whole data structure becomes more flexible, but without losing the overall standard. Through the response object, you can determine the data you want to return at a glance. Please refer to the following simple example:

{/ / public part "code" : 0, "MSG" : "success", "data" : {/ / business data}}Copy the code

Corresponding code:

@Data
public class BaseBean<T> {
    private Integer code;

    private String msg;

    private T data;
}
Copy the code

With generics, you have the flexibility to express any response

  • The user

    @getMapping ("/user") public BaseBean< user > user() {GetMapping("/user") public BaseBean< user > user = new BaseBean<>(); return user; }Copy the code
  • goods

    @getMapping (" goods") public BaseBean< goods > goods() {GetMapping(" goods") public BaseBean< goods > goods = new BaseBean<> goods(); return user; }Copy the code

    .

Well, today to share here, would like to see the friends of this article, in the future, no Bug!!