V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
morri
V2EX  ›  程序员

这个正则要怎么写呢?一个不标准的 json 字符串,想要手动给他修改正确

  •  
  •   morri ·
    1211ciel · 2022-07-24 12:49:12 +08:00 · 3177 次点击
    这是一个创建于 852 天前的主题,其中的信息可能已经有所发展或是发生改变。

    已经完成了键的修改,没把加""的都加上了, 但值要如何正则给加上""呢?

    func TestMakeToJsonStr(t *testing.T) {
    	str := `{label  :用户 id,searchType:1,hide:1,disabled:1,required:1,options:1:yes:tag-info,2:no:tag-danger}`
    	// 替换所有空格
    	// 处理 key 未加""的内容字段
    	replace, _ := gregex.Replace(`\s`, []byte(""), []byte(str))
    	replace, _ = gregex.Replace(`label|"label"`, []byte(`"label"`), replace)
    	replace, _ = gregex.Replace(`fieldType|"fieldType"`, []byte(`"fieldType"`), replace)
    	replace, _ = gregex.Replace(`searchType|"searchType"`, []byte(`"searchType"`), replace)
    	replace, _ = gregex.Replace(`editHide|"editHide"`, []byte(`"editHide"`), replace)
    	replace, _ = gregex.Replace(`addHide|"addHide"`, []byte(`"addHide"`), replace)
    	replace, _ = gregex.Replace(`hide|"hide"`, []byte(`"hide"`), replace)
    	replace, _ = gregex.Replace(`disabled|"disabled"`, []byte(`"disabled"`), replace)
    	replace, _ = gregex.Replace(`required|"required"`, []byte(`"required"`), replace)
    	replace, _ = gregex.Replace(`comment|"comment"`, []byte(`"comment"`), replace)
    	replace, _ = gregex.Replace(`options|"options"`, []byte(`"options"`), replace)
    	// 处理值未加个""的字段
    	fmt.Println(string(replace))
    }
    

    现在的执行结果

    {"label":用户 id,"searchType":1,"hide":1,"disabled":1,"required":1,"options":1:yes:tag-info,2:no:tag-danger}
    

    注明 键就是那几个是固定的

    35 条回复    2022-07-26 23:43:33 +08:00
    sutra
        1
    sutra  
       2022-07-24 14:50:49 +08:00
    Go's stdlib regexp engine is RE2 which does not support lookaround (e.g. the ?! negative lookahead operator).

    难啊。
    sutra
        2
    sutra  
       2022-07-24 16:20:05 +08:00   ❤️ 1
    sutra
        3
    sutra  
       2022-07-24 16:43:15 +08:00
    搞个第三方库,支持 ?! 就行了。
    sutra
        4
    sutra  
       2022-07-24 16:44:12 +08:00
    使用 regexp2 的 go 版本见上面 Java 版本后面的回复,v2ex 不让我贴太多 URL 。
    wxf666
        5
    wxf666  
       2022-07-24 16:44:29 +08:00
    #1 说这是 golang ?在 regex101 看了下,支持的正则特性有点少。。

    反正不要求通用方案,那就随便写咯


    『模式匹配』

    \s*"?(options)"?\s*:\s*"?(.*?)"?\s*(})|\s*"?(\w+)"?\s*:\s*"?(.*?)"?\s*(,)


    『全部替换为』

    \"$1$4\":\"$2$5\"$3$6


    『要求』

    1. 除了 options 外,其他键的值不包含『,』(锚定键值对结束)

    2. options 键在最后一位,其值不包含『}』(锚定键值对结束)

    3. 头尾裹上『"』后(若已有则不裹)仍符合 json 字符串规范

     (如:『 key: 他说"xxx",我不以为然』变成『"key": "他说"xxx",我不以为然"』会出问题)


    『例子』

    原文:

    {
      "label" : 用户 id ,
       searchType : "1" ,
      "hide" : 1 ,
       disabled : "1" ,
      "required" : "1" ,
       options:1:yes:tag-info,2:no:tag-danger
    }

    替换后:

    {"label":"用户 id","searchType":"1","hide":"1","disabled":"1","required":"1","options":"1:yes:tag-info,2:no:tag-danger"}
    morri
        6
    morri  
    OP
       2022-07-24 17:13:18 +08:00
    @sutra
    @wxf666 谢谢 我后面改改

    现在这样写的

    ```
    func makeToJsonStr(str string) string {
    // 替换所有空格
    replace, _ := gregex.Replace(`\s`, []byte(""), []byte(str))
    // 处理 key 未加""的内容字段
    replace, _ = gregex.Replace(`label|"label"`, []byte(`"label"`), replace)
    replace, _ = gregex.Replace(`fieldType|"fieldType"`, []byte(`"fieldType"`), replace)
    replace, _ = gregex.Replace(`searchType|"searchType"`, []byte(`"searchType"`), replace)
    replace, _ = gregex.Replace(`editHide|"editHide"`, []byte(`"editHide"`), replace)
    replace, _ = gregex.Replace(`addHide|"addHide"`, []byte(`"addHide"`), replace)
    replace, _ = gregex.Replace(`hide|"hide"`, []byte(`"hide"`), replace)
    replace, _ = gregex.Replace(`disabled|"disabled"`, []byte(`"disabled"`), replace)
    replace, _ = gregex.Replace(`required|"required"`, []byte(`"required"`), replace)
    replace, _ = gregex.Replace(`comment|"comment"`, []byte(`"comment"`), replace)
    replace, _ = gregex.Replace(`options|"options"`, []byte(`"options"`), replace)
    // 处理值未加个""的字段
    doAdd := func(temp string) []string {
    defer func() {
    if r := recover(); r != nil {
    panic(r)
    }
    }()
    strs := make([]string, 0)
    for _, i := range gstr.Split(temp, ",") {
    i = gstr.TrimAll(i)
    if i == "" {
    continue
    }
    begin := gstr.Split(i, ":")[0]
    end := gstr.Split(i, ":")[1]
    end = gstr.Replace(end, `"`, "")
    strs = append(strs, fmt.Sprintf(`%s:"%s"`, begin, end))
    }
    return strs
    }
    temp := string(replace)
    temp = temp[1 : len(temp)-1]
    strs := make([]string, 0)
    if !gstr.Contains(temp, `"options":`) {
    strs = append(strs, doAdd(temp)...)
    } else {
    t := gstr.Split(temp, `"options":`)
    strs = append(strs, doAdd(t[0])...)
    t[1] = gstr.Replace(t[1], `"`, "")
    t[1] = fmt.Sprintf(`"%s"`, t[1])
    strs = append(strs, fmt.Sprintf(`"options":%s`, t[1]))
    }
    return fmt.Sprintf(`{%s}`, strings.Join(strs, ","))
    }

    ```
    wxf666
        7
    wxf666  
       2022-07-24 17:35:03 +08:00   ❤️ 1
    @morri 临时学了下 golang ,看起来运行没问题

    『源码』

    package main

    import (
      "fmt"
      "regexp"
    )

    func main() {
       str := `{
        "label" : {label} ,
         searchType : "hide_222" ,
        "hide" : 333 disabled ,
         disabled : "required" ,
        "required" : "options" ,
         options:1:yes:tag-info,2:no:tag-danger
      }`
       re := regexp.MustCompile(`\s*"?(options)"?\s*:\s*"?(.*?)"?\s*(})|\s*"?(\w+)"?\s*:\s*"?(.*?)"?\s*(,)`)
       fmt.Println(re.ReplaceAllString(str, `"$1$4":"$2$5"$3$6`))
    }


    『输出』

    {"label":"{label}","searchType":"hide_222","hide":"333 disabled","disabled":"required","required":"options","options":"1:yes:tag-info,2:no:tag-danger"}
    morri
        8
    morri  
    OP
       2022-07-24 19:08:01 +08:00
    @wxf666 厉害上天~
    morri
        9
    morri  
    OP
       2022-07-24 19:22:33 +08:00
    @wxf666 键是固定的几个,但是位置可以随意变化的,因为都是可选。
    wxf666
        10
    wxf666  
       2022-07-24 19:27:41 +08:00
    @morri 那你要给出各字段值的特点才行啊

    否则产生的歧义,怕是连人工都分不清:

    {options:1:yes:tag-info,2:no:tag-danger,label:用户 id,searchType:1,hide:1,disabled:1,required:1}

    也可以认为是:

    {"options": "1:yes:tag-info,2:no:tag-danger,label:用户 id,searchType:1,hide:1,disabled:1,required:1"}
    morri
        11
    morri  
    OP
       2022-07-24 19:36:54 +08:00
    @wxf666
    最特别的值就是 `options:"1:yes:tag-info,2:no:tag-danger"`
    因为这个 key 如果有出现那么它的值格式是这样的`value1:label1:class1,value2,label2,class2...` 如果是在不好判断,就让 `options` 的值必须带 “” 双引号吧。让程序单独判断一下..
    wxf666
        12
    wxf666  
       2022-07-24 20:16:34 +08:00   ❤️ 1
    @morri 你试试这个:

    \s*"?(options)"?\s*:\s*"?((?:[^,]*?:[^,]*?:[^,]*?,?)*)"?\s*([,}])|\s*"?(\w+)"?\s*:\s*"?(.*?)"?\s*([,}])

    『要求』

    1. options 键的值,为若干个以『,』分隔的 value:label:class (每个字段都不包含『,』)

    2. 其他键的值不包含『,』『}』

    3. 头尾裹上『"』后(若已有则不裹)仍符合 json 字符串规范
    morri
        13
    morri  
    OP
       2022-07-24 21:03:42 +08:00
    @wxf666 多谢,可以~
    sutra
        14
    sutra  
       2022-07-24 22:35:13 +08:00
    只要有了 ?! ,且 option 的值里不包含其它 key: 就行。
    Kisesy
        15
    Kisesy  
       2022-07-24 22:49:46 +08:00
    我也遇到了一些奇特的格式,比如键没有引号,但是值有单引号,还有一种键和值都是单引号的
    各式各样的不规范编码
    wxf666
        16
    wxf666  
       2022-07-25 00:24:22 +08:00
    @sutra 我看 regex101 说,golang 的正则不支持断言,条件子组也不支持。你这是用了第三方库是嘛

    每匹配一个字前,都要看看后面的是否是一个关键字,我总觉得性能消耗会大一点

    另外,我老怀疑你『?!:』写错了……
    356693212
        17
    356693212  
       2022-07-25 00:58:33 +08:00
    1. 抽取 `{` 和 `}` 中的值
    2. `,` 分割 key 和 value
    3. 头尾加 `"`
    4. value 为 `{` .goto 1.
    5. 值转为 json
    sutra
        18
    sutra  
       2022-07-25 10:07:29 +08:00
    @wxf666 对呀,用了第三方库 regexp2 ,我在 #1 就说了,标准库不支持嘛。
    lmshl
        19
    lmshl  
       2022-07-25 11:11:32 +08:00
    如果这东西生成有规律,建议一步到位写 parser
    sutra
        20
    sutra  
       2022-07-25 12:14:02 +08:00
    @wxf666 我怀疑你怀疑得是对的。于是我修了一下。

    var keys = "label|fieldType|searchType|editHide|addHide|hide|disabled|required|comment|options";

    var regex = "(?<key>" + keys + ")\\s*:(?<value>(?:(?!(" + keys + "):).)*)(?<delimiter>[\\,\\}])";
    var replacement = "\"${key}\":\"${value}\"${delimiter}";
    ericmzhu
        21
    ericmzhu  
       2022-07-25 12:47:49 +08:00
    我觉得自己先解析字符串为 Json Data ,在输出成 string 好弄点
    ysc3839
        22
    ysc3839  
       2022-07-25 15:48:06 +08:00
    感觉原数据挺规整的,自己写个解析器也不难吧?网上有许多 JSON 解析器的教程,跟着写一个就好了。
    FYFX
        23
    FYFX  
       2022-07-25 16:23:29 +08:00
    我感觉也是写个 parser 比较好,用正则处理多重嵌套和换行感觉容易出问题,看描述大概也就写个 tokenizer ,然后对 token 判断一下类型确认是否加双引号就行
    joesonw
        24
    joesonw  
       2022-07-25 18:20:49 +08:00 via iPhone
    /([\w\d_]+\s*\:/
    wxf666
        25
    wxf666  
       2022-07-25 19:15:20 +08:00
    @lmshl
    @ericmzhu
    @ysc3839
    @FYFX

    编译原理新手求问,你们咋写词法和语法分析呢?


    比如下面这条,你们词法分析结果是啥呢?

    {options:1:yes:tag-info,2:no:tag-danger}


    这样吗?

    <{,左括号><options,关键字><:,冒号><1,数字><:,冒号><yes,字符串>……<},右括号>


    那你们文法咋写呢?

    1. 键值对 ::= 关键字 ":" 『???』

    2. 多个键值对 ::= 键值对 | 键值对 "," 多个键值对

    3. 对象 ::= "{" 多个键值对 "}"
    FYFX
        26
    FYFX  
       2022-07-25 20:26:11 +08:00
    @wxf666 按照 OP 说法,它这个是关键字固定的,而且 options 是特殊的其实应该在写 tokenizer 的时候做处理,应该是 5 个 token, "{","options",":","1:yes:tag-info,2:no:tag-danger","}",解析到 options 的时候做个特殊处理,在碰到下个关键字或者右花括号之前的塞到一个 token ,后面在 parser 再解析吧(也可以这步解析完作为一个特殊的 token),然后文法就和普通的 json 差不多了 ,大概长这样吧
    object ::= "{" member ("," member )* "}"
    member ::= keyword ":" (object|primary) | "options": options_value
    options_value ::= number ":" string ("," number":"string)*
    keyword ::= label|searchType|...
    primary ::= number|string
    我其实也是新手,最近在看 craftinginterpreters
    wxf666
        27
    wxf666  
       2022-07-25 21:18:27 +08:00
    @FYFX 词法分析有上下文相关的吗?
    FYFX
        28
    FYFX  
       2022-07-25 21:37:59 +08:00
    @wxf666 因为这不是规范的 json ,正常来讲这段应该是在两个引号直接的字符串,我只是觉得这么做后面 paser 写起来会简单点,当然你也可以在 paser 阶段把 options 后面那段拼起来
    wxf666
        29
    wxf666  
       2022-07-26 06:08:21 +08:00
    @FYFX 我这方面没啥经验。但总感觉,你的『解析 options 的值』这个步骤,适合放到语法分析中


    如你所说,『在碰到下个<keyword, 关键字>或者<}, 右花括号>之前……』,

    即『在碰到两种 Token 之前……』?


    另外,放到语法分析中,后续若想解析成 下列形式,也更容易些?(可扩展性强些?)

    1. {"options": {"1": "a:aa", "2": "b:bb"}}

    2. {"options": ["1:a:aa", "2:b:bb"]}


    好吧,如果放到词法分析中,要打算用啥方法解析 Token 呢?

    NFA/DFA ?应该不够用吧(也就是,三型文法的正则表达式,无法胜任了)

    LL/LR/SLR/LALR ?(我瞅瞅去)
    FYFX
        30
    FYFX  
       2022-07-26 09:59:34 +08:00 via iPad
    @wxf666 我只手写过 Scanner ,也就一个 while 循环从头扫到尾 http://www.craftinginterpreters.com/scanning.html
    lmshl
        31
    lmshl  
       2022-07-26 11:50:40 +08:00
    我写了一堆电子垃圾,勉强能解析但应该无法适应更多情况了,建议批判就行,别学
    面对这种数据格式,我建议打爆数据上游的狗头,让它改成标准 JSON 输出

    @wxf666

    https://gist.github.com/mingyang91/06f4a489c313a16b9285dd375a565808
    wxf666
        32
    wxf666  
       2022-07-26 17:38:13 +08:00
    @lmshl 瞅了瞅,没用过 scala ,只能大概看得懂

    这是扩展了自带正则库的文法规则,其递归下降去匹配?

    int 、bool 、double 、string 是词法分析,其余是语法分析?(感觉全说成是语法分析也无不可?)

    全部是三型文法?(因为没有递归?如:options ::= "options" ":" (options_tuple ("," options_tuple)* | options))

    也是高度依赖 tuple 有明确不同于其他文法的规则(要是 {options: opt1: val1, opt2: val2, label: user_id} 就完蛋了)


    估计上游不好好用 json 库,自己手动拼接去了,真的是搞事情
    lmshl
        33
    lmshl  
       2022-07-26 17:46:17 +08:00
    @wxf666 可以有递归,但是它这个文法里数组没有起止符,会和其他规则有冲突,如果是写标准 JSON parser 的话,代码能比这还少。
    最妖孽的就是他给的这个 options
    wxf666
        34
    wxf666  
       2022-07-26 20:31:27 +08:00
    @lmshl 懂的,都用递归下降了,肯定可以支持递归文法嘛

    上条回复我是说,你当前代码里,没有用到递归文法,所以应该是三型文法

    仅从『是否匹配』角度说,是可用『正则表达式』描述 你的文法 的(每个文法的结果处理函数就算了)
    morri
        35
    morri  
    OP
       2022-07-26 23:43:33 +08:00
    @wxf666 @lmshl @FYFX @sutra
    大佬们都厉害,向你们学习~
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1239 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 17:53 · PVG 01:53 · LAX 09:53 · JFK 12:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.