likes
comments
collection
share

后端返回的Map转成JavaScript对象时,保证原来顺序

作者站长头像
站长
· 阅读数 10

问题概述

一般情况下,一个下拉框里的options是后端返回的,有的时候后端返回的是一个数组,有的时候返回的却是一个对象,具体看后端怎么实现。

如果返回的是对象的话,我们需要将对象转换为数组,来进行循环,这时候就出现问题了😭。

假如后端返回如下数据,是正常的。

{
    "code": 0,
    "message": "Success",
    "zpData": {
        "0": "小米",
        "1": "苹果",
        "2": "华为",
        "3": "其他",
    },
}

由于这个对象是后端维护的,这个对象的key可能会增加一个,例如

{
    "code": 0,
    "message": "Success",
    "zpData": {
        "0": "小米",
        "1": "苹果",
        "2": "华为",
        "4": "oppo",
        "3": "其他",
    },
}

这个时候我们的下拉框就会展示成这个顺序

[小米, 苹果, 华为 ,其他, oppo]

产品说想把其他放在最后

问题分析

首先我们思考下为什么会出现这种情况呢?

后端返回的Map转成JavaScript对象时,保证原来顺序

当js解析json作为js对象时,会按照自己的规则对key进行排序:

如果key是整数(如:123)或者整数类型的字符串(如:“123”),那么会按照从小到大的排序。除此之外,其它数据类型,都安装对象key的实际创建顺序排序。

后端返回的Map转成JavaScript对象时,保证原来顺序

那怎么办呢?

我尝试了下Map, 发现Map可以保证顺序。

那么思路可以是这样:

  1. 获取到未被转换的响应数据

我们知道,axios 是基于 XMLHttpRequest 的,我们收到的响应数据不是直接就是json,需要我们通过JSON.parse解析

  1. 使用自定义的json解析方法

如果我们使用JSON.parse解析,就又会被解析成js对象。所以这里我们自己实现JSON.parse,返回一个Map保证顺序

代码实现

完整解析json的代码,返回的是个map

function myJSONParse(jsonString) {
    let index = 0;
    function parseObject() {
      const map = new Map();
      // 跳过左花括号
      index++;
      while (index < jsonString.length) {
        skipWhiteSpace();
        // 如果下一个字符是右花括号,表示对象解析完成
        if (jsonString[index] === '}') {
          index++;
          break;
        }
        // 解析键
        const key = parseString();
        skipWhiteSpace();
        // 跳过冒号
        if (jsonString[index] !== ':') {
          throw new SyntaxError('Expected \':\' at position ' + index);
        }
        index++;
        skipWhiteSpace();
        // 解析值
        const value = parseValue();
        map.set(key, value);
        skipWhiteSpace();
  
        // 如果下一个字符是逗号,表示还有更多的键值对
        if (jsonString[index] === ',') {
          index++;
          continue;
        }
  
        // 如果下一个字符不是逗号,那么对象解析完成
        if (jsonString[index] !== '}') {
          throw new SyntaxError('Expected \'}\' or \',\' at position ' + index);
        }
        index++;
        break;
      }
      return map;
    }
    function parseArray() {
      const arr = [];
      // 跳过左方括号
      index++;
      while (index < jsonString.length) {
        skipWhiteSpace();
        // 如果下一个字符是右方括号,表示数组解析完成
        if (jsonString[index] === ']') {
          index++;
          break;
        }
        // 解析值
        const value = parseValue();
        arr.push(value);
        skipWhiteSpace();
        // 如果下一个字符是逗号,表示还有更多的元素
        if (jsonString[index] === ',') {
          index++;
          continue;
        }
        // 如果下一个字符不是逗号,那么数组解析完成
        if (jsonString[index] !== ']') {
          throw new SyntaxError('Expected \']\' or \',\' at position ' + index);
        }
        index++;
        break;
      }
      return arr;
    }
    function parseString() {
      let result = '';
      let isInEscape = false;
      // 跳过引号
      index++;
      while (index < jsonString.length) {
        const char = jsonString[index];
        if (isInEscape) {
          result += char;
          isInEscape = false;
        } else {
          if (char === '\\') {
            isInEscape = true;
          } else if (char === '"') {
            // 解析完成
            index++;
            break;
          } else {
            result += char;
          }
        }
        index++;
      }
      return result;
    }
    function parseNumber() {
      let result = '';
      let char = jsonString[index];
      while (
        index < jsonString.length &&
        /[0-9.eE+\-]/.test(char)
      ) {
        result += char;
        index++;
        char = jsonString[index];
      }
      const num = Number(result);
      if (isNaN(num)) {
        throw new SyntaxError('Invalid number at position ' + index);
      }
      return num;
    }
    function parseValue() {
      skipWhiteSpace();
      const char = jsonString[index];
      switch (char) {
        case '{':
          return parseObject();
        case '[':
          return parseArray();
        case '"':
          return parseString();
        case '-':
        case '+':
        case '.':
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
          return parseNumber();
        case 't':
          if (jsonString.slice(index, index + 4) === 'true') {
            index += 4;
            return true;
          }
          break;
        case 'f':
          if (jsonString.slice(index, index + 5) === 'false') {
            index += 5;
            return false;
          }
          break;
        case 'n':
          if (jsonString.slice(index, index + 4) === 'null') {
            index += 4;
            return null;
          }
          break;
      }
      throw new SyntaxError('Unexpected token at position ' + index);
    }
    function skipWhiteSpace() {
      while (index < jsonString.length) {
        const char = jsonString[index];
        if (/\s/.test(char)) {
          index++;
        } else {
          break;
        }
      }
    }
  
    return parseValue();
  }
  

由于axios默认返回的是json格式,所以我们需要简单的配置

  export function dictGet(url, params = {}) {
    return axios(
        {
            method: 'get',
            url: url,
            params: params,
            transformResponse: [function (data) {
                // 对接收的 data 进行任意转换处理
                const map = myJSONParse(data);
                
                // 为了保证拦截器还能正常获取code,做出处理,这里外层还是使用json
                return {
                    code: map.get('code'),
                    message: map.get('message'),
                    data: map.get('data')
                };
              }]
        }
    );
  }

需要保证顺序序的接口就可以用

export const _getSomething = (params = {}) => {
    return dictGet('/api/downpull', params);
};

结语

需要保证有序的数据还是以数组的形式返回,这种方式只用于临时解决问题,😄。