之前做数据索引的时候接到一个需求:
我们在ES生产全文检索的索引的时候, 会定义mapping, 并依据mapping编写搜索逻辑, 并进行调优. 比如对于文章类的数据(任意业务方), 我们可以把它们的共性, 如标题, 副标题, 内容, 作者等固定为通用的mapping字段, 然后就可以很快的应用上基础的搜索能力了. 但是, 业务方的数据, 是各种各样的, 同样是标题类的数据, 有的叫做title, 有的叫做head; 再比如, 有的业务方希望自己数据的标题其实是 以及一级标题 + 二级标题 + tags 这样就给数据导入带来了问题, 需要给每个业务方编写数据转化, 操作的逻辑. 占用了大量的人力, 并且发布更新都很麻烦.
所以: 希望能有一种方式来简化这个流程, 让数据的导入过程更加简单, 减少人力和工作量.
让数据的导入过程更加简单 是终极业务需求, 我这里针对其中的一点: 给每个业务方编写数据转化, 操作的逻辑 阐述下我的解决思路.
举例:
用户提供的接口的数据:
{
"code" : 200,
"msg" : "success",
"result" : {
"data" : [
{
"id": 2,
"type" : "cat",
"title_one": "beautiful animal",
"title_two": "pets",
"content" : "i have a cat"
}
]
}
}
用户希望搜索标题的时候, 可以同时搜索"title_one", "title_two". 则我们需要一个设施可以操作用户的数据, 将"title_one", "title_two" 加和, 生成新的字段和数据.
这部分逻辑原先是用代码完成的, 代码是最灵活的, 可以满足各种需求. 那为了能够让开发同学任然具有这份灵活性, 我决定还是使用"代码" 的方式为他们提供支持, 类似规则引擎那样, 开发同学编写规则, 实际由规则引擎去执行具体的操作.
拆解为: 规则的解析 + 规则的执行
解析
规则的解析其实就是一个语言类应用嘛
语言类应用
● 文件读取器: 配置文件读取器, 方法调用分析工具之类的程序分析工具. java 的 class文件载入器, aroma
● 生成器: 收集内部数据结构信息, 产生输出. 对象-关系数据库映射工具, 序列化工具, 源代码生成器, 网页生成器
● 翻译器: = 读取器 + 生成器. 代码插装工具, 汇编器和编译器.
● 解释器: 读取文件, 解码, 执行指令. 计算器, python的实现.
如果要实现类似c语言风格的逻辑语言, 对我来说太难了, 还是借鉴了lisp
, 采用简单的语法结构: S表达式
, 关于lisp, 不去赘述, 我学的也不好....
但是希望可以用这样的方式来操作用户的json数据:
concat(max(list(2, 1, $user_data.clicknumber)), "something")
这段逻辑最终出来的数据是: 100someting, 一个json的str.
这是最简单的一个例子, 毕竟json还有object, list 等数据结构, 都要能够无损支持.
grammar CalcRefactorV1;
start : expr; // start rule, EOF if needed
// 没有类型检查
expr : list_expr
| not_list_expr
| compare_expr
| condition
| '(' expr ')'
;
list_expr: LIST '(' not_list_expr (',' not_list_expr)* ')' # ListCons
| FLATTEN '(' list_expr (',' list_expr)* ')' # ListFlatten
// | SUB '(' list_expr ')' # ListSub
| LIST_VARIABLE # ListVar
;
not_list_expr : NOT_LIST_VARIABLE # NotListVar
| CONCAT '(' not_list_expr (',' not_list_expr)* ')' # NotListConcat// 1 或多
// | CONCAT '(' list_expr ')'
| SIZE '(' list_expr ')' # ListSize
| JOIN '(' not_list_expr ',' list_expr ')' # ListJoin
| SUM '(' list_expr ')' # ListSum
| SUM '(' not_list_expr (',' not_list_expr)* ')' # NotListSum
| NUM # Num
| STRING # Str
;
//compare_expr : COMPARE '(' expr ',' expr ')' # Compare ;
compare_expr : COMPARE '(' not_list_expr ',' not_list_expr ')' # Compare ;
condition : CONDITION '(' compare_expr ',' expr ',' expr ')' # Condi ;
//condition : CONDITION '(' compare_expr ',' not_list_expr ',' not_list_expr ')' # Condi ;
// 简单字面量值
//value: NUM
// | STRING
//;
CONCAT: 'concat';
FLATTEN: 'flatten';
//SUB: 'sub';
SUM: 'sum';
LIST: 'list';
SIZE: 'size';
JOIN: 'join';
CONDITION: 'condition';
COMPARE: 'compare';
// list variable: $.[*] $.[*].xxx $.xx.yy12.[*] $.[*].xxx
// \$(\.[a-zA-Z_][a-zA-Z0-9_]*)*\.\[\*\]((\.[a-zA-Z_][a-zA-Z0-9_]*)|(\.\[\*\]))* ........ || 从这里开始的可以丢掉吧? 如果为了性能, 毕竟也不应该写这么复杂的
//LIST_VARIABLE: '$'('.'(ALPHA_)ALPHA__DIGIT*)*('.[*]'|('.'((ALPHA_)ALPHA__DIGIT*)?'['INT':'INT']'))(('.'(ALPHA_)ALPHA__DIGIT*)|'.[*]'|('.'((ALPHA_)ALPHA__DIGIT*)?'['INT':'INT']'))* ;
LIST_VARIABLE: '$'('.'ALPHA__DIGIT+)*('.[*]'|('.'(ALPHA__DIGIT+)?'['INT':'INT']'))(('.'ALPHA__DIGIT+)|'.[*]'|('.'(ALPHA__DIGIT+)?'['INT':'INT']'))* ;
// $.lll.
NOT_LIST_VARIABLE: '$'('.'ALPHA__DIGIT+)+;
NUM: '-'?(DIGIT+ | DIGIT+'.'DIGIT+ | '.'DIGIT);
STRING: '"'(ESC|.)*?'"';
WS: [ \t\n\r]+ -> skip;
fragment
DIGIT: [0-9];
INT: '0'|'-'?[1-9]DIGIT*;
ALPHA: [a-zA-Z];
ALPHA_: ALPHA|'_';
ALPHA__DIGIT: ALPHA_|DIGIT;
ESC: '\\"' | '\\\\';
关于list和非list表达式,也可以协作
LIST_VARIABLE: '$'('.'ALPHA__DIGIT+)*('.[*]'|('.'(ALPHA__DIGIT+)?'['INT':'INT']'))(('.'ALPHA__DIGIT+)|'.[*]'|('.'(ALPHA__DIGIT+)?'['INT':'INT']'))* ;
// $.lll.
NOT_LIST_VARIABLE: '$'('.'ALPHA__DIGIT+)+;
另外, 在pom里增加了一段处理罗辑, 每次编译前, 都要生成
<build>
<plugins>
<plugin>
<groupId>org.antlr</groupId>
<artifactId>antlr4-maven-plugin</artifactId>
<version>4.9.3</version>
<configuration>
<!--<sourceDirectory>${basedir}/src/java</sourceDirectory>-->
<outputDirectory>${basedir}/src/main/java/</outputDirectory>
<visitor>true</visitor>
<listener>true</listener>
</configuration>
<executions>
<execution>
<id>antlr</id>
<phase>process-sources</phase>
<goals>
<goal>antlr4</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
执行
package .................functions;
import ...........GsonJsonPathUtils;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import .....................gen.*;
/**
* This class provides an implementation and provides a way to calculate result of user defined expr: such as sum, size, etc.
* todo optimized
*/
public class CalculationVisitor extends CalcRefactorV1BaseVisitor<Function<String, JsonElement>> {
/**
* 暴露给上层的接口
*
* @param expr 用户定义的字符串表达式
*/
public static Function<String, JsonElement> parseExpr(String expr) {
CodePointCharStream input = CharStreams.fromString(expr);
CalcRefactorV1Lexer calcLexer = new CalcRefactorV1Lexer(input);
CommonTokenStream commonTokenStream = new CommonTokenStream(calcLexer);
CalcRefactorV1Parser calcParser = new CalcRefactorV1Parser(commonTokenStream);
ParseTree tree = calcParser.expr();
CalculationVisitor visitor = new CalculationVisitor();
Function<String, JsonElement> visit = visitor.visit(tree);
return visit;
}
/**
* @param ctx the parse tree
* @return func
*/
@Override
public Function<String, JsonElement> visitListCons(CalcRefactorV1Parser.ListConsContext ctx) {
List<CalcRefactorV1Parser.Not_list_exprContext> exprs = ctx.not_list_expr();
List<Function<String, JsonElement>> argsFuncs = exprs.stream().map(this::visit).collect(Collectors.toList());
return new ListCons(argsFuncs);
}
/**
* @param ctx the parse tree
* @return func
*/
@Override
public Function<String, JsonElement> visitListFlatten(CalcRefactorV1Parser.ListFlattenContext ctx) {
List<CalcRefactorV1Parser.List_exprContext> listExprContexts = ctx.list_expr();
List<Function<String, JsonElement>> argsFuncs = listExprContexts.stream().map(this::visit).collect(
Collectors.toList());
return new Flatten(argsFuncs);
}
/**
* @param ctx the parse tree
* @return func
*/
@Override
public Function<String, JsonElement> visitListVar(CalcRefactorV1Parser.ListVarContext ctx) {
String variable = ctx.getText();
return s -> GsonJsonPathUtils.read(s, variable);
}
/**
* @param ctx the parse tree
* @return func
*/
@Override
public Function<String, JsonElement> visitNotListVar(CalcRefactorV1Parser.NotListVarContext ctx) {
String variable = ctx.getText();
return s -> GsonJsonPathUtils.read(s, variable);
}
/**
* @param ctx the parse tree
* @return func
*/
@Override
public Function<String, JsonElement> visitNotListConcat(CalcRefactorV1Parser.NotListConcatContext ctx) {
List<CalcRefactorV1Parser.Not_list_exprContext> notListExprContexts = ctx.not_list_expr();
List<Function<String, JsonElement>> argsFuncs = notListExprContexts.stream().map(this::visit).collect(
Collectors.toList());
return new NotListConcat(argsFuncs);
}
/**
* @param ctx the parse tree
* @return func
*/
@Override
public Function<String, JsonElement> visitListSize(CalcRefactorV1Parser.ListSizeContext ctx) {
CalcRefactorV1Parser.List_exprContext listExprContext = ctx.list_expr();
Function<String, JsonElement> argsFunc = visit(listExprContext);
return new Size(argsFunc);
}
/**
* @param ctx the parse tree
* @return func
*/
@Override
public Function<String, JsonElement> visitListJoin(CalcRefactorV1Parser.ListJoinContext ctx) {
CalcRefactorV1Parser.Not_list_exprContext notListExprContext = ctx.not_list_expr();
CalcRefactorV1Parser.List_exprContext list_exprContext = ctx.list_expr();
Function<String, JsonElement> seperatorFunc = visit(notListExprContext);
Function<String, JsonElement> arrayFunc = visit(list_exprContext);
return new Join(seperatorFunc, arrayFunc);
}
/**
* @param ctx the parse tree
* @return func
*/
@Override
public Function<String, JsonElement> visitListSum(CalcRefactorV1Parser.ListSumContext ctx) {
Function<String, JsonElement> functions = visit(ctx.list_expr());
return new ListSum(functions);
}
/**
* @param ctx the parse tree
* @return func
*/
@Override
public Function<String, JsonElement> visitNotListSum(CalcRefactorV1Parser.NotListSumContext ctx) {
List<CalcRefactorV1Parser.Not_list_exprContext> notListExprContexts = ctx.not_list_expr();
List<Function<String, JsonElement>> ans = new LinkedList<>();
for (CalcRefactorV1Parser.Not_list_exprContext notListExprContext : notListExprContexts) {
Function<String, JsonElement> visit = visit(notListExprContext);
ans.add(visit);
}
return new NotListSum(ans);
}
/**
* @param ctx the parse tree
* @return func
*/
@Override
public Function<String, JsonElement> visitNum(CalcRefactorV1Parser.NumContext ctx) {
return s -> new JsonPrimitive(Integer.parseInt(ctx.NUM().getText()));
}
/**
* @param ctx the parse tree
* @return func
*/
@Override
public Function<String, JsonElement> visitStr(CalcRefactorV1Parser.StrContext ctx) {
return s -> new JsonPrimitive(ctx.getText().substring(1, ctx.getText().length() - 1));
}
}
附录
在antlr4的项目里面, 有很多的g4文件, 都是网友贡献的
这是json的语法文件, 接下来尝试写了个解析和判断json是否规范的代码.
解析json结构
/** Taken from "The Definitive ANTLR 4 Reference" by Terence Parr */
// Derived from http://json.org
grammar JSON;
json
: value EOF
;
obj
: '{' pair (',' pair)* '}'
// | '{' '}'
;
pair
: STRING ':' value
;
arr
: '[' value (',' value)* ']'
// | '[' ']'
;
value
: STRING
| NUMBER
| obj
| arr
| 'true'
| 'false'
| 'null'
;
STRING
: '"' (ESC | SAFECODEPOINT)* '"'
;
fragment ESC
: '\\' (["\\/bfnrt] | UNICODE)
;
fragment UNICODE
: 'u' HEX HEX HEX HEX
;
fragment HEX
: [0-9a-fA-F]
;
fragment SAFECODEPOINT
: ~ ["\\\u0000-\u001F]
;
NUMBER
: '-'? INT ('.' [0-9] +)? EXP?
;
fragment INT
: '0' | [1-9] [0-9]*
;
// no leading zeros
fragment EXP
: [Ee] [+\-]? INT
;
// \- since - means "range" inside [...]
WS
: [ \t\n\r] + -> skip
;
import .......gen.*;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
public class JsonStructureVisitor extends JSONBaseVisitor<DataStructure> {
public static DataStructure parseJson(String json) {
CodePointCharStream input = CharStreams.fromString(json);
JSONLexer lexer = new JSONLexer(input);
CommonTokenStream commonTokenStream = new CommonTokenStream(lexer);
JSONParser parser = new JSONParser(commonTokenStream);
ParseTree tree = parser.json();
JsonStructureVisitor visitor = new JsonStructureVisitor();
DataStructure visit = visitor.visit(tree);
return visit;
}
@Override
public DataStructure visitJson(JSONParser.JsonContext ctx) {
if (Objects.equals(ctx.value().getText(), "null")) {
return null;
}
return visit(ctx.value());
}
@Override
public DataStructure visitObj(JSONParser.ObjContext ctx) {
ObjectStructure obj = new ObjectStructure();
for (JSONParser.PairContext pairContext : ctx.pair()) {
DataStructure child = visit(pairContext.value());
if (child != null) {
obj.addField(pairContext.STRING().getText());
obj.addFieldType(child);
}
}
return obj;
}
@Override
public DataStructure visitPair(JSONParser.PairContext ctx) {
return super.visitPair(ctx);
}
@Override
public DataStructure visitArr(JSONParser.ArrContext ctx) {
ListStructure listStructure = new ListStructure();
Map<String, List<DataStructure>> collect = ctx.value().stream().map(this::visit).collect(
Collectors.groupingBy(x-> GsonInstance.getGson().toJson(x)));
if (collect.size() > 1) {
throw new RuntimeException(String.format("list: %s 中只能有一种类型", ctx.getText()));
}
listStructure.setItemType(collect.values().iterator().next().get(0));
return listStructure;
}
@Override
public DataStructure visitValue(JSONParser.ValueContext ctx) {
if (ctx.STRING() != null) {
return new PrimitiveStructure("string");
}
if (ctx.NUMBER() != null) {
return new PrimitiveStructure("number");
}
if (Objects.equals(ctx.getText(), "true") || Objects.equals(ctx.getText(), "false")) {
return new PrimitiveStructure("boolean");
}
return super.visitValue(ctx);
}
}
工具类
需要对json数据进行解析, 探查, 操作, 利用的是Gson + jsonpath
操作和解析
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.spi.json.GsonJsonProvider;
import com.jayway.jsonpath.spi.mapper.GsonMappingProvider;
import java.util.Map;
import java.util.Objects;
public class GsonJsonPathUtils {
private static final Configuration config;
static {
Configuration.ConfigurationBuilder builder = Configuration.builder();
builder.jsonProvider(new GsonJsonProvider());
builder.mappingProvider(new GsonMappingProvider());
config = builder.build();
}
/**
* todo: unfinished method, 给定某个数据结构, 验证path表达式, 是否合法
*
* @param ds the data structure
* @param jsonPathMap the json path map
* @param path the json path
*/
public static void constructAllJsonPath(DataStructure ds, Map<String, JsonPath> jsonPathMap, String path) {
if (Objects.equals(ds.type(), DataStructure.object)) {
ObjectStructure obj = (ObjectStructure) ds;
for (int i = 0; i < obj.getFields().size(); i++) {
String fieldName = obj.getField(i);
String newP = path + "." + fieldName;
jsonPathMap.put(newP, JsonPath.compile(path));
constructAllJsonPath(obj.getFieldsType(i), jsonPathMap, newP);
}
}
}
/**
* @param jsObj gson 的 json object
* @param jsPath compiled json path
* @param <T> type of result, jsObj, jsArray, jsPrimitive
* @return 从某个json中获取的值
*/
public static <T> T read(Object jsObj, JsonPath jsPath) {
return JsonPath.using(config).parse(jsObj).read(jsPath);
}
/**
* @param jsObj gson 的 json object
* @param jsPathStr have not compiled json path
* @param <T> type of result, jsObj, jsArray, jsPrimitive
* @return 从某个json中获取的值
*/
public static <T> T read(Object jsObj, String jsPathStr) {
return JsonPath.using(config).parse(jsObj).read(jsPathStr);
}
/**
* @param str gson 的 原始字符串
* @param jsPath compiled json path
* @param <T> type of result, jsObj, jsArray, jsPrimitive
* @return 从某个json中获取的值
*/
public static <T> T read(String str, JsonPath jsPath) {
return JsonPath.using(config).parse(str).read(jsPath);
}
/**
* @param str gson 的 json object
* @param jsPathStr haven't compiled json path
* @param <T> type of result, jsObj, jsArray, jsPrimitive
* @return 从某个json中获取的值
*/
public static <T> T read(String str, String jsPathStr) {
return JsonPath.using(config).parse(str).read(jsPathStr);
}
/**
* @param str gson 的 原始字符串
* @param jsPathStr compiled json path
* @return 获取json中的这个路径代表的元素的长度, 一定得是数组, 否则抛异常
*/
public static int length(String str, String jsPathStr) {
return JsonPath.using(config).parse(str).read(jsPathStr + ".length()");
}
/**
* @param jsObj gson 的 json object
* @param jsPathStr haven't compiled json path
* @return 获取json中的这个路径代表的元素的长度, 一定得是数组, 否则抛异常
*/
public static int length(Object jsObj, String jsPathStr) {
return JsonPath.using(config).parse(jsObj).read(jsPathStr + ".length()");
}
}
注册自定义数据类型和解析器
public static final GsonBuilder builder;
public static final Gson gson;
static {
builder = new GsonBuilder();
builder.registerTypeAdapter(DataStructure.class, new GsonDataStructureSerde());
gson = builder.create();
}
自定义序列化, 反序列化
数据类型
这个类, 可以用来表示json的结构:object, list, primitive.
import com.alibaba.fastjson.annotation.JSONType;
//@JSONType(seeAlso = {ListStructure.class, ObjectStructure.class, PrimitiveStructure.class})
// NOTICE: 如果有自引用的话, equals 和 toString 方法, 会导致StackOverFlow 或者 oom
public abstract class DataStructure {
public static final String list = "list";
public static final String primitive = "primitive";
public static final String object = "object";
private final String type;
public DataStructure(String type) {
this.type = type;
}
public String type() {
return this.type;
}
public boolean isPrimitive() {
return this.type.equals(primitive);
}
public boolean isList() {
return this.type.equals(list);
}
public boolean isObject() {
return this.type.equals(object);
}
public abstract boolean selfValidate();
}
import com.alibaba.fastjson.annotation.JSONType;
import java.util.Objects;
//@JSONType(typeName = "list")
public class ListStructure extends DataStructure {
public DataStructure itemType;
public ListStructure() {
super(list);
}
public void setItemType(DataStructure itemType) {
this.itemType = itemType;
}
// NOTICE: 如果有自引用的话, equals 和 toString 方法, 会导致StackOverFlow 或者 oom
@Override
public String toString() {
return "ListStructure{" +
"itemType=" + itemType +
'}';
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj instanceof ListStructure) {
ListStructure other = (ListStructure) obj;
return Objects.equals(this.itemType, other.itemType);
}
return false;
}
@Override
public boolean selfValidate() {
return itemType != null && itemType.selfValidate();
}
}
import com.alibaba.fastjson.annotation.JSONType;
import lombok.Getter;
import java.util.*;
import java.util.function.Consumer;
@Getter
//@JSONType(typeName = "object")
public class ObjectStructure extends DataStructure implements Iterable<AbstractMap.SimpleImmutableEntry<String, DataStructure>> {
public List<String> fields = new ArrayList<>();
public List<DataStructure> fieldsType = new ArrayList<>();
public ObjectStructure() {
super(object);
}
public void addField(String key) {
this.fields.add(key);
}
public void addFieldType(DataStructure type) {
this.fieldsType.add(type);
}
// NOTICE: 如果有自引用的话, equals 和 toString 方法, 会导致StackOverFlow 或者 oom
@Override
public String toString() {
return "ObjectStructure{" +
"fields=" + fields +
", fieldsType=" + fieldsType +
'}';
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj instanceof ObjectStructure) {
ObjectStructure other = (ObjectStructure) obj;
boolean b1 = this.fields.size() == other.fields.size();
boolean b2 = this.fieldsType.size() == other.fieldsType.size();
if (!(b1 && b2)) {
return false;
}
for (int i = 0; i < this.fields.size(); i++) {
String field = this.fields.get(i);
Optional<DataStructure> thisFieldType = this.getFieldType(field);
Optional<DataStructure> otherFieldType = other.getFieldType(field);
Boolean isEqual = thisFieldType.flatMap(t -> otherFieldType.map(t::equals)).orElse(false);
if (!isEqual) {
return false;
}
}
return true;
}
return false;
}
public Optional<DataStructure> getFieldType(String fieldName) {
int i = fields.indexOf(fieldName);
return (i >= 0) ? Optional.of(fieldsType.get(i)) : Optional.empty();
}
public String getField(int i) {
return this.fields.get(i);
}
public DataStructure getFieldsType(int i) {
return this.fieldsType.get(i);
}
@Override
public Iterator<AbstractMap.SimpleImmutableEntry<String, DataStructure>> iterator() {
return new Iterator<AbstractMap.SimpleImmutableEntry<String, DataStructure>>() {
private int i = 0;
@Override
public boolean hasNext() {
return i < fields.size();
}
@Override
public AbstractMap.SimpleImmutableEntry<String, DataStructure> next() {
i += 1;
return new AbstractMap.SimpleImmutableEntry<>(fields.get(i - 1), fieldsType.get(i - 1));
}
};
}
@Override
public boolean selfValidate() {
return fields.size() == fieldsType.size() && fieldsType.stream().allMatch(DataStructure::selfValidate);
}
}
import com.alibaba.fastjson.annotation.JSONType;
import io.swagger.v3.oas.models.media.Schema;
import java.util.*;
//@JSONType(typeName = "primitive")
public class PrimitiveStructure extends DataStructure {
private String pType = "string"; // number, string, boolean // todo date
private String format = ""; // byte,short, integer,long, unsigned_long, double, float, // todo unknown, 浮点 + 整型
public static final List<String> pTypes = Arrays.asList("string", "number", "boolean");
public static final List<String> swaggerTypes = Collections.unmodifiableList(Arrays.asList("string", "number", "boolean", "integer"));
public static PrimitiveStructure swaggerSchema2DataStructureConverter(Schema<?> schema) {
assert PrimitiveStructure.swaggerTypes.contains(schema.getType()); // 将所有非object, array的结构都认作原子/基本结构
String type;
String format = "";
if (Objects.equals(schema.getType(), "string")) {
type = "string";
} else if (Objects.equals(schema.getType(), "number")) {
type = "number";
format = schema.getFormat() == null ? "" : schema.getFormat();
} else if (Objects.equals(schema.getType(), "integer")) {
type = "number";
format = schema.getFormat() != null ? Objects.equals(schema.getFormat(), "int32") ? "integer" : "long" : "integer";
} else if (Objects.equals(schema.getType(), "boolean")) {
type = "boolean";
} else {
throw new RuntimeException("unknown swagger type: " + schema.getType());
}
PrimitiveStructure primitiveStructure = new PrimitiveStructure(type);
primitiveStructure.setFormat(format);
return primitiveStructure;
}
// 在swagger文档中是这样定义数据类型的
// https://swagger.io/docs/specification/data-models/data-types/
//number – Any numbers.
//number float Floating-point numbers.
//number double Floating-point numbers with double precision.
//integer – Integer numbers.
//integer int32 Signed 32-bit integers (commonly used integer type).
//integer int64 Signed 64-bit integers (long type).
public PrimitiveStructure(String pType) {
super(primitive);
if (!pTypes.contains(pType)) {
throw new RuntimeException("unknown pType: " + pType);
}
this.pType = pType;
}
// NOTICE: 如果有自引用的话, equals 和 toString 方法, 会导致StackOverFlow 或者 oom
@Override
public String toString() {
return "PrimitiveStructure{" + "pType='" + pType + '\'' + ", format='" + format + '\'' + '}';
}
@Override
public boolean selfValidate() {
boolean p = Objects.equals(pType, "string") || Objects.equals(pType, "number") || Objects.equals(pType, "boolean");
boolean f = format != null;
return p && f;
}
public void setFormat(String format) {
this.format = format;
}
public String getPType() {
return this.pType;
}
public String getFormat() {
return this.format;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj instanceof PrimitiveStructure) {
PrimitiveStructure other = (PrimitiveStructure) obj;
return Objects.equals(this.pType, other.pType);
}
return false;
}
}```