首页 > 解决方案 > 清理坏的 UTF-8 字符串

问题描述

由于格式错误的用户数据,我的gRPC服务未能发送请求。原来 HR 用户数据有一个错误的UTF-8字符串,gRPC无法对其进行编码。我将坏字段缩小到这个字符串:

"Gr\351gory Smith" // Gr�gory Smith  (this is coming from an LDAP source)

所以我想要一种方法来清理这些输入,如果它们包含错误的UTF-8编码。

在标准包中没有看到任何明显的消毒功能unicode/utf8,这是我第一次天真的尝试:

func naïveSanitizer(in string) (out string) {
    for _, rune := range in {
        out += string(rune)
    }
    return
}

输出:

Before: Valid UTF-8? false  Name: 'Gr�gory Smith' Byte-Count:  13
After:  Valid UTF-8? true   Name: 'Gr�gory Smith' Byte-Count:  15

游乐场版

UTF-8有没有更好或更标准的方法来从坏字符串中挽救尽可能多的有效数据?


我在这里暂停的原因是因为在迭代字符串并且遇到错误(第 3 个)字符时,utf8.ValidRune(rune)返回true: https: //play.golang.org/p/_FZzeTRLVls

所以我的后续问题是,迭代一个字符串 - 一次一个符文 - 符文值是否始终有效?即使底层的源字符串编码格式错误?


编辑:

澄清一下,这些数据来自 LDAP 源:500K 用户记录。在这 50 万条记录中,只有 15 条(十五条),即约 0.03%返回uf8.ValidString(...).false

正如@kostix 和@peterSO 所指出的,如果从另一种编码(例如Latin-1)转换为UTF-8,这些值可能是有效的。将此理论应用于这些异常样本:

https://play.golang.org/p/9BA7W7qQcV3

Name:     "Jean-Fran\u00e7ois Smith" : (good UTF-8) :            : Jean-François Smith
Name:                   "Gr\xe9gory" : (bad  UTF-8) : Latin-1-Fix: Grégory
Name:               "Fr\xe9d\xe9ric" : (bad  UTF-8) : Latin-1-Fix: Frédéric
Name:                 "Fern\xe1ndez" : (bad  UTF-8) : Latin-1-Fix: Fernández
Name:                     "Gra\xf1a" : (bad  UTF-8) : Latin-1-Fix: Graña
Name:                     "Mu\xf1oz" : (bad  UTF-8) : Latin-1-Fix: Muñoz
Name:                     "P\xe9rez" : (bad  UTF-8) : Latin-1-Fix: Pérez
Name:                    "Garc\xeda" : (bad  UTF-8) : Latin-1-Fix: García
Name:                  "Gro\xdfmann" : (bad  UTF-8) : Latin-1-Fix: Großmann
Name:                     "Ure\xf1a" : (bad  UTF-8) : Latin-1-Fix: Ureña
Name:                    "Iba\xf1ez" : (bad  UTF-8) : Latin-1-Fix: Ibañez
Name:                     "Nu\xf1ez" : (bad  UTF-8) : Latin-1-Fix: Nuñez
Name:                     "Ba\xd1on" : (bad  UTF-8) : Latin-1-Fix: BaÑon
Name:                  "Gonz\xe1lez" : (bad  UTF-8) : Latin-1-Fix: González
Name:                    "Garc\xeda" : (bad  UTF-8) : Latin-1-Fix: García
Name:                 "Guti\xe9rrez" : (bad  UTF-8) : Latin-1-Fix: Gutiérrez
Name:                      "D\xedaz" : (bad  UTF-8) : Latin-1-Fix: Díaz
Name:               "Encarnaci\xf3n" : (bad  UTF-8) : Latin-1-Fix: Encarnación

标签: stringgoutf-8utf

解决方案


Go 1.13 引入了strings.ToValidUTF8(),所以sanitizer()应该是:

func sanitize(s string) string {
    return strings.ToValidUTF8(s, "")
}

我什至认为它不应该有它自己的功能。在Go Playground上尝试一下。

如果您的输入恰好是字节切片,您可以使用类似的bytes.ToValidUTF8()功能。

另请注意,如果您不只是想从输入中丢弃一些数据而没有跟踪,则可以在调用时使用任何替换字符(或多个字符)strings.ToValidUTF8(),例如:

return strings.ToValidUTF8(in, "❗")

在Go Playground上试试这个。


推荐阅读