我的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和@petSO指出的那样,如果将这些值从另一编码(例如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
答案 0 :(得分:2)
您可以通过删除无效的符文来改善您的“消毒剂”:
package main
import (
"fmt"
"strings"
)
func notSoNaïveSanitizer(s string) string {
var b strings.Builder
for _, c := range s {
if c == '\uFFFD' {
continue
}
b.WriteRune(c)
}
return b.String()
}
func main() {
fmt.Println(notSoNaïveSanitizer("Gr\351gory Smith"))
}
问题是\351
是Latin-1中的字符é。
@PeterSO指出它也恰好在Unicode的BMP中的同一位置,这是正确的,但是Unicode不是一种编码,并且您的数据据称已编码,所以我认为您对此的假设不正确。数据编码,它不是UTF-8,而是Latin-1(或与拉丁字母重读字母兼容的东西)。
因此,我确认您确实在处理Latin-1(或其他),如果是这样,
golang.org/x/text/encoding
提供了完整的工具,可以将传统编码重新编码为UTF-8(或其他格式)。
(顺便说一句,您可能也不会碰巧明确要求您的数据源为您提供UTF-8编码的数据。)
答案 1 :(得分:2)
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上尝试这个。
答案 2 :(得分:1)
解决您的问题。 \351
是Unicode代码点é
的八进制值。
package main
import "fmt"
func main() {
fmt.Println(string(rune(0351)))
fullname := "Grégory Smith" // "Gr\351gory Smith"
fmt.Println(fullname)
}
游乐场:https://play.golang.org/p/WigFZk3iSK1
输出:
é
Grégory Smith