从字符串中提取泛型类型参数



我想创建一个从类型定义中提取"泛型类型参数"的函数(作为纯字符串(。

它应该采用如下输入字符串:

Foo<Bar, Baz<Qux>>

并返回一个带有引用类型 + 泛型的对象,如下所示(当然不必采用这种确切格式,只要我可以检索所需的信息(:

{
   "name": "Foo",
   "generics": [
      {
         "name": "Bar",
         "generics": []
      },
      {
         "name": "Baz",
         "generics": [
            {
               "name": "Qux",
               "generics": []
            }
         ]
      }
   ]
}

我的猜测是将String.match/<.*>/g 这样的正则表达式一起使用,用逗号作为分隔符拆分结果,并递归解析每个参数的泛型。但是,我觉得这太复杂了,而且我错过了更简单的方法。

最简单的方法是递归构建一个键映射结构,然后将其转换为树。

下面的keyMapToTree函数使用名为 keyMapToTreeInner 的内部帮助程序函数。

console.log(keyMapToTree(parseAsKeyMap('Foo<Bar, Baz<Qux>>')));
function parseAsKeyMap(input, tree = {}) {
  input = input.trim();
  let startIndex = input.indexOf('<'),
    endIndex   = input.lastIndexOf('>');
  if (startIndex !== -1 && endIndex === -1) {
    throw new Error("Missing closing bracket '>' for " + input);
  } else if (startIndex === -1 && endIndex !== -1) {
    throw new Error("Missing opening bracket '<' for " + input);
  } else if (startIndex !== -1 && endIndex !== -1) {
    let head = input.substring(0, startIndex),
      tail = input.substring(startIndex + 1, endIndex);
    tree[head] = {};
    tail.split(/s*,s*/).forEach(token => parseAsKeyMap(token, tree[head]));
  } else {
    tree[input] = {};
  }
  return tree;
}
function keyMapToTree(input) {
  let keys = Object.keys(input);
  if (keys.length !== 1) {
    throw new Error('Object must be non-null and have only one key!');
  }
  let key = keys[0], node = { name: key, generics: [] };
  keyMapToTreeInner(input[key], node.generics);
  return node;
}
function keyMapToTreeInner(input, nodeArray) {
  Object.keys(input).map(key => {
    let node = { name: key, generics: [] };
    keyMapToTreeInner(input[key], node.generics);
    nodeArray.push(node)
  });
}
.as-console-wrapper {
  top: 0;
  max-height: 100% !important;
}
<!--
The initial key-map will look like this, so convert this structure to a tree.
{
  "Foo": {
    "Bar": {},
    "Baz": {
      "Qux": {}
    }
  }
}
-->

深受Polywhirl先生的回答的启发,我创建了以下实现:

(为清楚起见,带有打字稿类型注释(

type TypeInfo = { //the returned object format
    name: string;
    generics: TypeInfo[];
}
function parseGenerics(input: string): TypeInfo {
    input = input.trim();
    const startIndex = input.indexOf('<'),
          endIndex = input.lastIndexOf('>');
    if (startIndex !== -1 && endIndex === -1) {
        throw new Error("Missing closing bracket '>' for " + input);
    } else if (startIndex === -1 && endIndex !== -1) {
        throw new Error("Missing opening bracket '<' for " + input);
    } else if (startIndex === -1 && endIndex === -1) { //no generics
        return {
            name: input,
            generics: []
        };
    } else {
        const head = input.substring(0, startIndex),
              tail = input.substring(startIndex + 1, endIndex);
        return {
            name: head,
            generics: tail.split(/s*,s*/).map(parseGenerics)
        };
    }
}

使用 Foo<Bar, Baz<Qux>> 作为输入,这将导致:

{
    "name": "Foo",
    "generics": [
        {
            "name": "Bar",
            "generics": []
        },
        {
            "name": "Baz",
            "generics": [
                {
                    "name": "Qux",
                    "generics": []
                }
            ]
        }
    ]
}

我更喜欢这种实现而不是 Polywhirl 先生的实现,因为它会立即创建正确的数据格式,而不需要额外的转换步骤。这使得它(在我看来(是一个更干净、更精简的解决方案。

如果您是 Chrome 用户,则此代码可在控制台中工作:

// let inputString = "Foo<Bar, Baz<Qux<Some, Thing<Else<But, Not>, So<Is>>, Other>>>"
let inputString = "Foo<Bar, Baz<Qux>>"
const replacements = {}
let replacementIndex = 0
while (true) {
  const replacement = (inputString.match(/[A-Z][a-z0-9]+<(([A-Z][a-z0-9]+)[,s]*)+>/) || [])[0]
  if (replacement) {
    const key = `Key${replacementIndex}`
    replacementIndex++
    replacements[key] = replacement
    inputString = inputString.replace(replacement, key)
  } else {
    break
  }
}
const resultJson = {}
const parseSentence = (text) => {
  const [key, valuesStr] = text.replace(/>$/, '').split(/</)
  const values = valuesStr.split(',').map((x) => x.trim())
  return {
    [key]: values,
  }
}
Object.keys(replacements).forEach((key) => {
  resultJson[key] = parseSentence(replacements[key])
})
while (true) {
  let replacementsFound = false
  Object.keys(resultJson).forEach((key) => {
    Object.keys(resultJson[key]).forEach((name) => {
      resultJson[key][name] = resultJson[key][name].map((value) => {
        if (/^Key[d+]$/.test(value)) {
          replacementsFound = true
          return resultJson[value]
        }
        return value
      })
    })
  })
  if (!replacementsFound) {
    break
  }
}
const resultKey = `Key${replacementIndex - 1}`
const unpreparedResult = resultJson[resultKey]
const prepareResultJson = (json) => {
  const name = Object.keys(json)[0]
  const generics = []
  json[name].forEach((generic) => {
    if (typeof generic === 'string') {
      generics.push({ name: generic, generics: [] })
    } else {
      generics.push(prepareResultJson(generic))
    }
  })
  const result = {
    name,
    generics,
  }
  return result
}
const finalResult = prepareResultJson(unpreparedResult)
console.log(finalResult)

您也可以关注此网址:https://codepen.io/SergioBelevskij/pen/ZPdVyM

最新更新