Aviator表达式扩展--数组、列表负向序号索引以及自己的JSON库支持方括号([])赋值

1. 背景

笔者开发的大数据大数据平台XSailboat 中的DataStudio模块包含基于DAG图的实时计算可视化开发功能,实现了Flink计算任务的可视化开发(想要了解更多,可以查看《Flink的DAG可视化开发实践》)

我们都知道,原生的Flink任务开发适合在IDE中使用代码去开发调试,想要去做可视化是很难的。为此引入了“模式框架+Aviator表达式”的可视化思路,为的就是让Flink任务适合界面开发,又不过多失去原生Flink的表达能力。

Aviator是一个非常优秀的表达式语言,适合应用在需要轻度编程的场景下。在我们的大数据平台中的计算管道开发、数据服务开发、还有其它一些需要前端轻度表达数据处理逻辑的地方,都有使用它。

Aviator表达式扩展--数组、列表负向序号索引以及自己的JSON库支持方括号([])赋值_第1张图片

  • 为什么需要负向序号索引?
    Aviator原生的功能是不带有负向序号索引的功能的,在没有负向索引的情况下,想要取数组的最后一个元素,就得这么写:
let arr = seq.array(int , 1, 2, 3 , 4) ;
## 打印最后一个元素
p(arr[len(a) - 1]) ;

负向序号索引的目标就是为了让它变得更简洁,对于配置界面来说,尽可能简洁是非常有意义的。所以我们对序号索引操作符([])进行了重载,使其支持负向序号索引,达到了下面的目标:

let arr = seq.array(int , 1, 2, 3 , 4) ;
## 打印最后一个元素
p(arr[-1]) ;
  • 为什么需要使得Aviator表达式对我们自己的JSON库支持方括号([])场景下的赋值?
    Aviator表达式,在新版本(5.3)下是支持以下特性的。
let arr = seq.array(int , 1, 2, 3 , 4) ;
let mp = seq.map()
## 直接赋值
arr[0] = -1 ;
mp['key'] = -1 ;

## 遍历元素
for e in arr{
  p(e) ;
}
## 遍历键值
for k , v in mp{
  p(k+'='+v) ;
}

JSON是一种非常常见的数据格式。在实践中发现,CSV+JSON是一种非常好的数据组织格式,它能解决个性化和字段对齐的矛盾。举一个例子:
我们需要开发某种业务领域的专家策略规则,规则数量很多,在应用过程中还会不断扩展。每种规则都有一些公共属性,但又都有自己个性化的一些配置项。这种数据该如何存储到数据库?
在对这种具有“模型”性质的数据,我们后台通常会使用Spring JPA去定义模型Bean,去做增、删、改、查操作。那么思路就是定义一个规则配置(ruleConf)字段,这个字段是一个类RuleConf,然后不同的规则继承RuleConf实现自己的规则配置类,如RuleConf_001、RuleConf_002等。这个ruleConf字段在持久化到数据库的时候,就变成JSON字符串存到这一列中。这样就实现了在一个整齐的表格中存储信息不整齐的数据。
表格就可以看成一种CSV,在我们的Kafka主题中,主题都是CSV格式,而其中的某一列可能是基本类型,也可能是JSON字符串。

而在整个平台中,对JSON字符串的解析和操作,我们有自己的JSON库。在微服务集群场景下,对JSON的操作会成为开发的日常,所以我们不断丰富和扩展JSON库的能力,使得JSON操作尽可能简洁、快速、高效。

在没有扩展之前,我们只能这么实现同样功能的代码:

let ja = j.array(1, 2, 3 , 4) ;
let jo = j.object()
## 直接赋值
j.array.put(ja , -1 , 0) ;
j.object.put(jo , 'key' , -1) ;
## 获取
p(j.array.opt(ja . 0)) ;
p(j.object.opt(jo , 'key')) ;

## 遍历元素
j.array.each(lambda(e)->p(e); end) ;
## 遍历键值
j.object.each(lambda(k , v)->p(k+'='+v) ;end)

在扩展之后,我们就能这样写这些代码:

let ja = j.array(1, 2, 3 , 4) ;
let jo = j.object()
## 直接赋值
ja[0] = -1 ;
jo['key'] = -1 ;
## 获取
p(ja[0]) ;
p(jo['key'])) ;

## 遍历元素
for e in ja{
  p(e) ;
}
## 遍历键值
for k , v in jo{
  p(k+'='+v) ;
}

2. 代码实现

  • 重载索引操作符([])
package com.cimstech.sailboat.aviator;

import java.lang.reflect.Array;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import com.cimstech.xfront.common.json.JSONArray;
import com.cimstech.xfront.common.log.Assert;
import com.cimstech.xfront.common.math.Bool;
import com.cimstech.xfront.common.reflect.XClassUtil;
import com.googlecode.aviator.lexer.token.OperatorType;
import com.googlecode.aviator.runtime.type.AviatorObject;
import com.googlecode.aviator.runtime.type.AviatorRuntimeJavaElementType;
import com.googlecode.aviator.runtime.type.AviatorRuntimeJavaElementType.ContainerType;
import com.googlecode.aviator.runtime.type.AviatorType;

public class IndexOverload extends WedgeFunction
{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	@Override
	public String getName()
	{
		return OperatorType.INDEX.getToken();
	}

	@Override
	public AviatorObject call(Map<String, Object> aEnv, AviatorObject aArg1, AviatorObject aArg2)
	{
		if (aArg1.getAviatorType() == AviatorType.JavaType)
		{
			Object obj = aArg1.getValue(aEnv);
			if (obj != null)
			{
				if (obj instanceof JSONArray)
				{
					Integer index = XClassUtil.toInteger(aArg2.getValue(aEnv));
					Assert.notNull(index, "序号不能为null。%s", aArg2.toString());
					// 负向索引
					if (index < 0)
					{
						index += ((JSONArray) obj).length();
					}
					return new AviatorJaRuntimeJavaType((JSONArray) obj, index);
				}
				// 对List和Array的[]重载,是为了支持负向索引。例如a[-1] 等价a[size-1]
				else if (obj instanceof List)
				{
					Integer index = XClassUtil.toInteger(aArg2.getValue(aEnv));
					Assert.notNull(index, "序号不能为null。%s", aArg2.toString());
					// 负向索引
					if (index < 0)
					{
						index += ((List) obj).size();
					}
					final Integer index0 = index;
					// 用这种形式返回,就能支持a[0] = 'hello' 这种设置方法
					return new AviatorRuntimeJavaElementType(ContainerType.List,
							obj,
							index0,
							new Callable<Object>()
							{
								@Override
								public Object call() throws Exception
								{
									return ((List<?>) obj).get(index0);
								}
							});
				}
				else if (obj.getClass().isArray())
				{
					Integer index = XClassUtil.toInteger(aArg2.getValue(aEnv));
					Assert.notNull(index, "序号不能为null。%s", aArg2.toString());
					// 负向索引
					if (index < 0)
					{
						index += Array.getLength(obj);
					}
					final Integer index0 = index;
					// 用这种形式返回,就能支持a[0] = 'hello' 这种设置方法
					return new AviatorRuntimeJavaElementType(ContainerType.Array,
							obj,
							index0,
							new Callable<Object>()
							{
								@Override
								public Object call() throws Exception
								{
									return Array.get(obj, index0);
								}
							});
				}
			}
		}
		if (wedges != null)
		{
			Bool support = new Bool(false);
			for (IWedge wedge : wedges)
			{
				AviatorObject result = wedge.call(aEnv, aArg1, aArg2, support);
				if (support.get())
					return result;
			}
		}
		return aArg1.getElement(aEnv, aArg2);
	}
}

上面的代码,之所以没有出现JSONObject是因为我们的JSONObject实现了java.util.Map接口,它自然就具备了Aviator关于Map的所有操作和语法。而之所以为出现JSONArray,是因为我们的JSONArray并没有实现java.util.List接口,这主要是因为JSONArray上已经有很多和List同质但不同名的方法,此处并没有绝对的必要去实现List接口。

对于JSONArray对象的ja[0] = -1这种设置语法,通过下面的类支持上:

  • com.cimstech.sailboat.aviator.AviatorJaRuntimeJavaType
package com.cimstech.sailboat.aviator;

import java.util.Map;

import com.cimstech.xfront.common.json.JSONArray;
import com.googlecode.aviator.runtime.type.AviatorObject;
import com.googlecode.aviator.runtime.type.AviatorRuntimeJavaType;

public class AviatorJaRuntimeJavaType extends AviatorRuntimeJavaType
{

	private static final long serialVersionUID = 1L;
	
	private final int mIndex;

	
	public AviatorJaRuntimeJavaType(final JSONArray aContainer, final int aIndex)
	{
		super(null);
		object = aContainer;
		mIndex = aIndex;
		callable = this::get;
	}

	Object get()
	{
		return ((JSONArray) object).get(mIndex);
	}

	/**
	 * a[0] = -1 这种赋值语法会调用此方法
	 */
	@Override
	public AviatorObject setValue(final AviatorObject aValue, final Map<String, Object> aEnv)
	{
		return AviatorRuntimeJavaType.valueOf(((JSONArray) object).put(mIndex, aValue.getValue(aEnv)));
	}
}

而要应用上这些扩展,只需要在程序/模块初始化的时候,调用如下代码:

AviatorEvaluator.getInstance().addOpFunction(OperatorType.INDEX , new IndexOverload()) ; 

你可能感兴趣的:(大数据平台开发技术,json,java)