String 清理坏的UTF-8字符串

String 清理坏的UTF-8字符串,string,go,utf-8,utf,String,Go,Utf 8,Utf,由于用户数据格式不正确,MygRPC服务无法发送请求。原来HR用户数据有一个坏的UTF-8字符串,gRPC无法对其进行编码。我将坏字段缩小为以下字符串: "Gr\351gory Smith" // Gr�gory Smith (this is coming from an LDAP source) 所以我想要一种方法来净化这些输入,如果它们包含坏的UTF-8编码 在unicode/utf8标准包中没有看到任何明显的消毒功能,下面是我第一次天真的尝试: func naïveSanitizer(

由于用户数据格式不正确,My
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
é
Grégory Smith

是否有更好或更标准的方法从坏
UTF-8
字符串中恢复尽可能多的有效数据


我之所以在这里暂停,是因为在迭代字符串时遇到错误(第3个)字符,
utf8.ValidRune(符文)
返回
true

所以我的后续问题是,迭代一个字符串-一次迭代一个符文-符文值是否始终有效?即使基础源字符串编码格式不正确


编辑:

只是澄清一下,这些数据来自LDAP源:500K用户记录。在这些500K记录中,只有15(十五)条(即约0.03%)返回
uf8。有效字符串(…)
false

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


解决你的问题
\351
是Unicode代码点
的八进制值

package main

import "fmt"

func main() {
    fmt.Println(string(rune(0351)))
    fullname := "Grégory Smith" // "Gr\351gory Smith"
    fmt.Println(fullname)
}
游乐场:

输出:

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

解决你的问题
\351
是Unicode代码点
的八进制值

package main

import "fmt"

func main() {
    fmt.Println(string(rune(0351)))
    fullname := "Grégory Smith" // "Gr\351gory Smith"
    fmt.Println(fullname)
}
游乐场:

输出:

Before: Valid UTF-8? false  Name: 'Gr�gory Smith' Byte-Count:  13
After:  Valid UTF-8? true   Name: 'Gr�gory Smith' Byte-Count:  15
é
Grégory Smith
您可以通过删除无效符文来改进“消毒剂”:

主程序包
进口(
“fmt”
“字符串”
)
func notSoNaïveSanitizer(s弦)弦{
var b strings.Builder
对于u,c:=范围s{
如果c=='\uFFFD'{
持续
}
b、 书面材料(c)
}
返回b.String()
}
func main(){
fmt.Println(诺索纳维萨尼泽(“Gr\351格雷史密斯”))
}

但问题是,
\351
是中的字符

@PeterSO指出,它也恰好位于Unicode的BMP中的相同位置,这是正确的,但Unicode不是一种编码,您的数据应该是编码的,所以我认为您对数据的编码有一个错误的假设,它不是UTF-8,而是拉丁-1(或与拉丁重音字母兼容的东西)

所以我要确认你真的在处理拉丁语-1(或其他什么),如果是的话, 提供完整的工具,用于将传统编码重新编码为UTF-8(或其他)

(顺便说一句,你最好不要碰巧明确要求你的数据源提供UTF-8编码的数据。)

你可以通过删除无效符文来改进你的“消毒剂”:

主程序包
进口(
“fmt”
“字符串”
)
func notSoNaïveSanitizer(s弦)弦{
var b strings.Builder
对于u,c:=范围s{
如果c=='\uFFFD'{
持续
}
b、 书面材料(c)
}
返回b.String()
}
func main(){
fmt.Println(诺索纳维萨尼泽(“Gr\351格雷史密斯”))
}

但问题是,
\351
是中的字符

@PeterSO指出,它也恰好位于Unicode的BMP中的相同位置,这是正确的,但Unicode不是一种编码,您的数据应该是编码的,所以我认为您对数据的编码有一个错误的假设,它不是UTF-8,而是拉丁-1(或与拉丁重音字母兼容的东西)

所以我要确认你真的在处理拉丁语-1(或其他什么),如果是的话, 提供完整的工具,用于将传统编码重新编码为UTF-8(或其他)

(顺便说一句,您最好不要碰巧明确要求您的数据源为您提供UTF-8编码的数据。)

Go 1.13引入,因此
sanitizer()
应该是:

func sanitize(s string) string {
    return strings.ToValidUTF8(s, "")
}
我甚至认为它不应该有自己的功能。试一下

如果您的输入恰好是一个字节片,您可以使用类似的函数

还请注意,如果您不想在没有任何痕迹的情况下丢弃输入中的某些数据,则在调用
strings.ToValidUTF8()
时,可以使用任何替换字符(或多个字符),例如:

return strings.ToValidUTF8(in, "❗")
试试这一款。

Go 1.13,因此
消毒剂()
应该是:

func sanitize(s string) string {
    return strings.ToValidUTF8(s, "")
}
我甚至认为它不应该有自己的功能。试一下

如果您的输入恰好是一个字节片,您可以使用类似的函数

还请注意,如果您不想在没有任何痕迹的情况下丢弃输入中的某些数据,则在调用
strings.ToValidUTF8()
时,可以使用任何替换字符(或多个字符),例如:

return strings.ToValidUTF8(in, "❗")

在上试试这个。

我没有解决方案,但问题是在字符串上使用
range
已经假设它应该是有效的,因此它用U+FFFD“替换字符”替换任何无效的字节序列符文。这对于
ValidRune
来说是有效的,因为它可以被编码。所以你需要一种方法,它不会试图在字符串上
范围
。根据@hobbs所说的,如果c=='\uFFFD'{continue;},我就用
扩展你的消毒剂
并宣布这一天结束。我认为UTF-8不坏,只是它不是UTF-8。我认为这个人的名字是格雷戈里·史密斯,这就是我们给你设定的。无论谁把它读成UTF-8,都是在犯错误。修复这个错误,你就不会有问题,也不会弄坏这个人的名字。@TomBlodget我编辑了Q,并提供了更多细节:它是糟糕的UTF-8——但谢天谢地,只有一小部分。在这里添加了“顽皮字符串的大列表”(自从我访问这个问题以来)