js读取外部json指定字段值完整代码_shell脚本:json格式化与字段抓取(下)

接上一篇

2 字段抓取

2.1 规则制定

先规定字段抓取的规则,对于给定的json

  • .:表示整个json
  • .fieldName:表示抓取fieldName字段的值,可能是字符串、布尔值、数字,或子json对象;
  • .[]:如果json或子json对象是数组,表示获取数组的所有元素;
  • .[0]:如果json或子json对象是数组,表示获取数组的第一个元素,其它.[1].[2]以此类推;
  • 以上可以复合使用,如.[].fieldName1.[0].fieldName2,需要最外层json是一个数组,表示获取最外层数组所有元素的fieldName1字段的数组的第一个元素的fieldName2字段值。

目前就考虑这些功能。

2.2 shell 实现

可以跟上一篇里面的shell代码放在一起。

# ------------------- function --------------------
# 函数:抓取(过滤)出json指定字段值
# 入参1:json
# 入参2:过滤条件,支持“.”,“.[]”,“.fieldName”,“.[index]”,及其组合
# 入参3:是否需要检查json合法性,0:不需要;其它:需要
# 入参4:缩进量,格式化时用到
fetch() {
    JSON=$1
    FILTER=$2
    NEED_CHECK=$3
    FORMATTED_JSON=
    # 检查json是否合法
    if [ "0" != "$NEED_CHECK" ]; then
        FORMATTED_JSON=`format "$JSON" "$4"`
    else
        FORMATTED_JSON=$JSON
    fi
    # 读取上一条命令return的执行状态,非0表示异常
    if [ $? != 0 ]; then
        echo "Error [line:$LINENO]: invalid json"
        return 1
    fi
    # .直接返回全部json
    if [ "." = "$FILTER" ]; then
        echo "$FORMATTED_JSON"
        return 0
    fi
    # 将复合过滤条件以.号拆分成单个的过滤条件数组
    # 所有的.号替换成空格,加上()即可自动变成数组
    FILTER_ARRAY=(${FILTER//./ })
    # 遍历所有单个的过滤条件
    for(( i=0; i<${#FILTER_ARRAY[*]}; i++ ))
    do
        CURRENT_FILTER=${FILTER_ARRAY[$i]}
        # []的情况
        # 对数组的各个元素递归
        if [  "[]" = "$CURRENT_FILTER" ]; then
            # 检查json是否为数组
            if [ "[" != "${FORMATTED_JSON:0:1}" ]; then
                echo "Error [line:$LINENO]: not an array"
                return 1
            fi
            CHILD_JSON_ARRAY=`getChildrenJson "$FORMATTED_JSON"`
            # 后续的过滤条件
            REMAINING_FILTER=
            for(( j=`expr $i + 1`; j<${#FILTER_ARRAY[*]}; j++ ))
            do
                REMAINING_FILTER="${REMAINING_FILTER}.${FILTER_ARRAY[$j]}"
            done
            # 对每个 child 传入后续的过滤条件递归
            RESULT="["
            for CHILD in ${CHILD_JSON_ARRAY[*]}
            do
                # 递归调用
                RESULT="${RESULT}`fetch "$CHILD" "$REMAINING_FILTER" 0`,"
            done
            # 去掉最后一个“,”加上“]”
            # “%,*”表示从右侧开始,去掉最后一个“,”及右边的字符
            RESULT="${RESULT%,*}]"
            # 后续过滤条件已经做完,直接返回
            echo "$RESULT"
            return 0
        # [0],[1]...的情况
        # 似乎没有[0-9]+的写法,用[[0-9][0-9]*代替
        elif [ `echo "$CURRENT_FILTER" | grep "^[[0-9][0-9]*]$"` ]; then
            # 读取指定的数组元素
            # “#[”删除最左边的[
            INDEX=${CURRENT_FILTER#[}
            INDEX=${INDEX%]}
            FORMATTED_JSON=`getChildrenJson "$FORMATTED_JSON" "$INDEX"`
        # 其它都当做读取字段处理
        else
            FORMATTED_JSON=`getFieldValue "$FORMATTED_JSON" "$CURRENT_FILTER"`
        fi
    done
    echo "$FORMATTED_JSON"
    return 0
}

# 函数:获取数组json的所有子json,或指定位置的子json
# 入参1:json,必须是数组
# 入参2:index,指定想要的子json的位置,从0开始,不指定则获取全部
# 出参:json数组
getChildrenJson() {
    JSON=$1
    INDEX=$2
    RESULT_JSON_ARRAY=()
    # []{}块,每关闭一个{}块,即为一个子json,根[块关闭之后则为扫描结束
    BLOCK_ARRAY=('[')
    CURRENT=
    CHILD_JSON=
    # 从1开始,因为第0位的[已经记录到BLOCK_ARRAY里面去了
    JSON_LENGTH=${#JSON}
    POSITION=1
    while (( ${POSITION}<${JSON_LENGTH} ))
    do
        CURRENT=${JSON:POSITION:1}
        CHILD_JSON="${CHILD_JSON}${CURRENT}"
        LAST_BLOCK_INDEX=`expr ${#BLOCK_ARRAY[*]} - 1`
        CURRENT_BLOCK=${BLOCK_ARRAY[$LAST_BLOCK_INDEX]}
        if [ """ = "$CURRENT_BLOCK" ]; then
            if [ """ = "$CURRENT" ]; then
                unset BLOCK_ARRAY[$LAST_BLOCK_INDEX]
                if [ ${#BLOCK_ARRAY[*]} == 1 ]; then
                    # 如果指定了index参数,并且此处恰好就是,则返回当前 child
                    if [ "${#RESULT_JSON_ARRAY[*]}" = "$INDEX" ]; then
                        echo "$CHILD_JSON"
                        return 0
                    fi
                    # child 添加到数组
                    RESULT_JSON_ARRAY[${#RESULT_JSON_ARRAY[*]}]="$CHILD_JSON"
                    # 清空 child
                    CHILD_JSON=
                    # 如果下一个字符是逗号,跳过
                    NEXT_POSITION=`expr $POSITION + 1`
                    if [ "," = "${JSON:NEXT_POSITION:1}" ]; then
                        let POSITION++
                    fi
                fi
            fi
            let POSITION++
            continue
        fi
        if [ "{" = "$CURRENT" ]; then
            # 记录一个{}块
            BLOCK_ARRAY[${#BLOCK_ARRAY[*]}]='{'
        elif [ "}" = "$CURRENT" ]; then
            # 关闭一个{}块
            unset BLOCK_ARRAY[`expr ${#BLOCK_ARRAY[*]} - 1`]
            # 退出到了根[]块,表示一个child已经结束
            if [ ${#BLOCK_ARRAY[*]} == 1 ]; then
                # 如果指定了index参数,并且此处恰好就是,则返回当前 child
                if [ "${#RESULT_JSON_ARRAY[*]}" = "$INDEX" ]; then
                    echo "$CHILD_JSON"
                    return 0
                fi
                # child 添加到数组
                RESULT_JSON_ARRAY[${#RESULT_JSON_ARRAY[*]}]="$CHILD_JSON" 
                # 清空 child
                CHILD_JSON=
                # 如果下一个字符是逗号,跳过
                NEXT_POSITION=`expr $POSITION + 1`
                if [ "," = "${JSON:NEXT_POSITION:1}" ]; then
                    let POSITION++
                fi                
            fi
        elif [ """ = "$CURRENT" ]; then
            BLOCK_ARRAY[${#BLOCK_ARRAY[*]}]="""
        elif [ "," = "$CURRENT" ]; then
            # 当前在根[]块,表示一个child已经结束
            if [ ${#BLOCK_ARRAY[*]} == 1 ]; then
                # 删除已经添加进去的逗号
                CHILD_JSON="${CHILD_JSON%,}"
                # 如果指定了index参数,并且此处恰好就是,则返回当前 child
                if [ "${#RESULT_JSON_ARRAY[*]}" = "$INDEX" ]; then
                    echo "$CHILD_JSON"
                    return 0
                fi
                # child 添加到数组
                RESULT_JSON_ARRAY[${#RESULT_JSON_ARRAY[*]}]="$CHILD_JSON"
                # 清空 child
                CHILD_JSON=
            fi
        fi
        let POSITION++
    done
    # 走到这里说明没有传 index,或 index 越界
    if [ "$INDEX" ]; then
        echo "Error [line:$LINENO]: array out of bounds"
        exit 1
    fi
    echo "${RESULT_JSON_ARRAY[*]}"
    return 0
}

# 函数:获取指定字段名称的值,默认json中同一层次不存在相同的字段名称
# 入参1:json
# 入参2:字段名称
# 出参:指定字段名称的值
getFieldValue() {
    JSON=$1
    FIELD=$2
    VALUE=
    POSITION=0
    LENGTH=${#JSON}
    FIELD_LENGTH=${#FIELD}
    # 字段名称长度 + "": 的长度3个字符
    MATCH_LENGTH=`expr $FIELD_LENGTH + 3`
    while(( $POSITION<$LENGTH ))
    do
        MATCH_STR=${JSON:POSITION:MATCH_LENGTH}
        # 匹配到了
        if [ "$MATCH_STR" = ""$FIELD":" ]; then
            POSITION=`expr $POSITION + $MATCH_LENGTH`
            # 向后读取 value
            BLOCK_ARRAY=()
            CURRENT=${JSON:POSITION:1}
            # 如果 value 的第一位不是{,[,"的任何一个,则直接开始读取,读到,或}结束
            if [[ "{" != "$CURRENT" && "[" != "$CURRENT" && """ != "$CURRENT" ]]; then
                VALUE="$CURRENT"
                let POSITION++
                while(( $POSITION<$LENGTH  ))
                do
                    CURRENT=${JSON:POSITION:1}
                    if [[ "," = "$CURRENT" || "}" = "$CURRENT" ]]; then
                        echo "$VALUE"
                        return 0
                    else
                        VALUE="${VALUE}${CURRENT}"
                    fi
                    let POSITION++
                done
                echo "Error [line:$LINENO]: unknown error, maybe bug"
                return 1
            fi
            # 读取第一个{,[,或",直到退出为止
            BLOCK_ARRAY[0]="$CURRENT"
            VALUE="$CURRENT"
            let POSITION++
            while(( ${#BLOCK_ARRAY[*]} != 0 ))
            do
                CURRENT=${JSON:POSITION:1}
                VALUE="${VALUE}${CURRENT}"
                if [[ "}" = "$CURRENT" || "]" = "$CURRENT" ]]; then
                    unset BLOCK_ARRAY[`expr ${#BLOCK_ARRAY[*]} - 1`]
                elif [[ "{" = "$CURRENT" || "[" = "$CURRENT" ]]; then
                    BLOCK_ARRAY[${#BLOCK_ARRAY[*]}]="$CURRENT"
                elif [ """ = "$CURRENT" ]; then
                    LAST_BLOCK_INDEX=`expr ${#BLOCK_ARRAY[*]} - 1`
                    CURRENT_BLOCK=${BLOCK_ARRAY[$LAST_BLOCK_INDEX]}
                    if [ """ = "$CURRENT_BLOCK" ]; then
                        unset BLOCK_ARRAY[$LAST_BLOCK_INDEX]
                    else
                        BLOCK_ARRAY[${#BLOCK_ARRAY[*]}]="""
                    fi
                fi
                let POSITION++
                if [ $POSITION -ge $LENGTH ]; then
                    break
                fi
            done
            echo "$VALUE"
            return 0
        fi
        let POSITION++
    done
    echo "Error [line:$LINENO]: not found field $FIELD"
    return 1
}

# 函数:去掉所有空白字符
# 入参1:字符串
# 出参:去掉所有空白字符的字符串
clearBlank() {
    STR=$1
    RESULT=
    POSITION=0
    LENGTH=${#STR}
    while(( $POSITION<$LENGTH ))
    do
        CURRENT=${STR:POSITION:1}
        if [ $CURRENT ]; then
            RESULT="${RESULT}${CURRENT}"
        fi
        let POSITION++
    done
    echo "$RESULT"
    return 0
}

修改主流程:

# --------------------- main ----------------------
# 读取主流程参数
# 入参1:json
# 入参2:缩进字符数,默认为2
# 入参3:字段过滤器
echo -e `fatch "$1" "$3" "1" "$2"`
# --------------------- main ----------------------

现在,执行

pj '{"a":"b"}' 4 ".a"

即可返回a字段值b

3 设置命令参数

现在我们有三个参数了:json,缩进量,字段抓取的过滤器,有时候可能容易搞混掉传错顺序。

再添加一些命令行参数说明。

加一个help函数,提示命令使用方式:

# ------------------- function --------------------
# 函数:打印命令提示
help() {
echo "$0 [-i INDENT] [-f FILTER] [-d JSON] [-c]

Format json and fetch field value.
OPTIONS:
        -i      indent, 2 characters as default
        -f      filter, e.g. .fieldName1.[0].fieldName2.[].fieldName3
        -d      data, json
        -c      check if json is valid
" >&2

        exit 1
}
# ------------------- function --------------------

再修改主流程:

# --------------------- main ----------------------
# 入参
JSON=
INDENT=
FILTER=
CHECK=

# 读取命令行参数
# 冒号表示该参数需要传值,如此处 -i -f -d 参数
while getopts "i:f:d:c" opt; do
    # 参数在 opt 变量里面
    case $opt in
        i)
            # 参数后的值存放在 OPTARG 变量中
            INDENT=$OPTARG
            ;;
        f)
            FILTER=$OPTARG
            ;;
        d)
            JSON=$OPTARG
            ;;
        c)
            CHECK=1
            ;;
        ?)
            help
            ;;
    esac
done

if [ ! "$JSON" ]; then
    echo "Error: json missing"
    exit 1
fi
if [ ! "$INDENT" ]; then
    INDENT=2
fi
if [ ! "$FILTER" ]; then
    FILTER="."
fi

# 对于确定 json 一定是正确的格式的情况下,检查就有点多余
# 只有传了 -c 参数,才检查json是否合法
if [ $CHECK ]; then
    CHECK_RESULT=`format "$JSON"`
    if [ $? != 0 ]; then
        echo "Error [line:$LINENO]: invalid json"
        exit 1
    fi
fi
# 清除所有空白字符,否则目前的逻辑可能会对字段抓取的准确性造成一定影响
JSON=`clearBlank "$JSON"`
# 抓取字段
MAIN_RESULT=`fetch "$JSON" "$FILTER" "0" "$INDENT"`
# 尝试格式化
FORMATTED_MAIN_RESULT=`format "$MAIN_RESULT" "$INDENT"`
# 如果抓取的字段已经不是 json,格式化会失败,则返回非格式化的字段
if [ $? == 0 ]; then
    echo -e "$FORMATTED_MAIN_RESULT"
else
    echo -e "$MAIN_RESULT"
fi
# --------------------- main ----------------------

现在,使用

pj -d '{"a":"b"}' -i 4 -f .a -c

pj -d '{"a":"b"}' -f .a

即可抓取到字段a的值。

当然,也可以使用更复杂的json尝试。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值