V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Sponsored by
LinkedIn
2000 个不用坐班的远程好工作在召唤你 · 弹性上班不打卡,工作和生活都能拥有
2000 个不用坐班的全球远程工作,帮助 V2EX 的小伙伴开启全新的工作方式。
Promoted by LinkedIn
morri
V2EX  ›  程序员

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

  •  
  •   morri · 62 天前 · 2529 次点击
    这是一个创建于 62 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    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  
       62 天前
    Go's stdlib regexp engine is RE2 which does not support lookaround (e.g. the ?! negative lookahead operator).

    难啊。
    sutra
        2
    sutra  
       62 天前   ❤️ 1
    给你个 Java 版本的:
    sutra
        3
    sutra  
       62 天前
    搞个第三方库,支持 ?! 就行了。
    sutra
        4
    sutra  
       62 天前
    使用 regexp2 的 go 版本见上面 Java 版本后面的回复,v2ex 不让我贴太多 URL 。
    wxf666
        5
    wxf666  
       62 天前
    #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
       62 天前
    @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  
       62 天前   ❤️ 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
       62 天前
    @wxf666 厉害上天~
    morri
        9
    morri  
    OP
       62 天前
    @wxf666 键是固定的几个,但是位置可以随意变化的,因为都是可选。
    wxf666
        10
    wxf666  
       62 天前
    @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
       62 天前
    @wxf666
    最特别的值就是 `options:"1:yes:tag-info,2:no:tag-danger"`
    因为这个 key 如果有出现那么它的值格式是这样的`value1:label1:class1,value2,label2,class2...` 如果是在不好判断,就让 `options` 的值必须带 “” 双引号吧。让程序单独判断一下..
    wxf666
        12
    wxf666  
       62 天前   ❤️ 1
    @morri 你试试这个:

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

    『要求』

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

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

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

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

    另外,我老怀疑你『?!:』写错了……
    356693212
        17
    356693212  
       62 天前
    1. 抽取 `{` 和 `}` 中的值
    2. `,` 分割 key 和 value
    3. 头尾加 `"`
    4. value 为 `{` .goto 1.
    5. 值转为 json
    sutra
        18
    sutra  
       62 天前
    @wxf666 对呀,用了第三方库 regexp2 ,我在 #1 就说了,标准库不支持嘛。
    lmshl
        19
    lmshl  
       61 天前
    如果这东西生成有规律,建议一步到位写 parser
    sutra
        20
    sutra  
       61 天前
    @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  
       61 天前
    我觉得自己先解析字符串为 Json Data ,在输出成 string 好弄点
    ysc3839
        22
    ysc3839  
       61 天前
    感觉原数据挺规整的,自己写个解析器也不难吧?网上有许多 JSON 解析器的教程,跟着写一个就好了。
    FYFX
        23
    FYFX  
       61 天前
    我感觉也是写个 parser 比较好,用正则处理多重嵌套和换行感觉容易出问题,看描述大概也就写个 tokenizer ,然后对 token 判断一下类型确认是否加双引号就行
    joesonw
        24
    joesonw  
       61 天前 via iPhone
    /([\w\d_]+\s*\:/
    wxf666
        25
    wxf666  
       61 天前
    @lmshl
    @ericmzhu
    @ysc3839
    @FYFX

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


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

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


    这样吗?

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


    那你们文法咋写呢?

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

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

    3. 对象 ::= "{" 多个键值对 "}"
    FYFX
        26
    FYFX  
       61 天前
    @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  
       61 天前
    @FYFX 词法分析有上下文相关的吗?
    FYFX
        28
    FYFX  
       61 天前
    @wxf666 因为这不是规范的 json ,正常来讲这段应该是在两个引号直接的字符串,我只是觉得这么做后面 paser 写起来会简单点,当然你也可以在 paser 阶段把 options 后面那段拼起来
    wxf666
        29
    wxf666  
       61 天前
    @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  
       61 天前 via iPad
    @wxf666 我只手写过 Scanner ,也就一个 while 循环从头扫到尾 http://www.craftinginterpreters.com/scanning.html
    lmshl
        31
    lmshl  
       60 天前
    我写了一堆电子垃圾,勉强能解析但应该无法适应更多情况了,建议批判就行,别学
    面对这种数据格式,我建议打爆数据上游的狗头,让它改成标准 JSON 输出

    @wxf666

    wxf666
        32
    wxf666  
       60 天前
    @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  
       60 天前
    @wxf666 可以有递归,但是它这个文法里数组没有起止符,会和其他规则有冲突,如果是写标准 JSON parser 的话,代码能比这还少。
    最妖孽的就是他给的这个 options
    wxf666
        34
    wxf666  
       60 天前
    @lmshl 懂的,都用递归下降了,肯定可以支持递归文法嘛

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

    仅从『是否匹配』角度说,是可用『正则表达式』描述 你的文法 的(每个文法的结果处理函数就算了)
    morri
        35
    morri  
    OP
       60 天前
    @wxf666 @lmshl @FYFX @sutra
    大佬们都厉害,向你们学习~
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2436 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 49ms · UTC 02:53 · PVG 10:53 · LAX 19:53 · JFK 22:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.