Ниже история одной моей довольно забавной ошибки. Воистину, программист никогда не заскучает; даже имея в руках идеальный инструмент, он сможет применить его неправильно. Что уж говорить об инструментах неидеальных?
Может показаться удивительным, но в просвещённом 2020-ом году всё ещё существуют разработчики, которые полагают, что нет ничего криминального в использовании кодировки windows-1251 при отдаче с эндпойнта API контента, содержащего кириллицу. Бог им судья, мне же пришлось искать способ этот самый контент безболезненно конвертнуть в благословенный юникод.
Забегая вперёд скажу, что задача, по сути, была примитивнейшая, однако я был бы не я, если бы не устроил себе цирк на ровном месте.
Итак, берём входящие байты и конвертим их в юникод (обработка ошибок и прочие закрытия body убраны для краткости).
dec := charmap.Windows1251.NewDecoder()
out, _ := dec.Bytes(a)
var meter dmc.Meter
_ = json.Unmarshal(out, &meter)
log.Print(meter.MeterName)
Что я ожидал увидеть? “ЭЛЕКТРОСНАБЖЕНИЕ”(я кажется, не упоминал, что работаю в ЖЭКе). А что я увидел? “пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ”
Признаться, результат меня озадачил и я кинулся искать, где и то пошло не так, однако все предлагаемые гуглом способы давали неизменно один и тот же результат. Начальник едко отметил, что процесс у меня идемпотентный - какой бы способ перекодировки не применялся, получается неизменно говно, и ушёл домой, я же остался чесать в затылке.
Тут меня осенило - если от смены способа перекодировки результат не меняется, значит, проблема прячется где-то в другом месте. Минут пятнадцать я пытался понять, могут ли разработчики того самого API присылать в хедере кодировку Windows-1251, а в тексте что-либо другое, однако Postman убедил меня, что, по крайней мере, тут всё честно - контент закодирован именно в Windows-1251. Тогда я решил проследить весь путь данных, от получения до вывода в консоль и почти сразу принялся драть волосы из бороды, проклиная собственную тупость.
Следите за руками. Контент приходит в формате json, такой вот структуры:
{
"contents": [],
"result": {
"code": 1,
"message": "some message"
}
}
Поскольку я использую строго типизированный Go, и меня больше интересует не служебная информация, а тот самый контент, то данные проходят следующую обработку (я опускаю нерелевантный заметке код):
type Result struct {
Code int
Message string
}
type Response struct {
Contents []interface{}
Result Result
}
content := [][]byte{}
var response Response
_ = json.Unmarshal(res_body, &response)
for _, item := range response.Contents {
item_bytes, _ := json.Marshal(item)
content = append(content, item_bytes)
}
И вот этот самый content
и есть то, с чем уже можно работать,
маршалить его в структуры, отправлять письмом на деревню дедушке, и т.д.,
но вот тут-то и спряталась ошибка: первый анмаршаллинг пришедших байтов
сконвертил кириллицу в совершенно нечитаемые иероглифы, поскольку
корректно он умеет работать только с юникодом. А далее я получившиеся
каракули собственноручно смаршалил в байты и попытался сконвертировать
заранее нечитаемые символы в юникод и вполне ожидаемо получил на
выходе именно его - говно на лопате.
Как понимаете, после того, как я перенёс перекодирования данных в самое начало, сразу после после их получения, ситуация исправилась:
resp, _ := client.Get(url)
res_body, _ := ioutil.ReadAll(resp.Body)
dec := charmap.Windows1251.NewDecoder()
out, _ := dec.Bytes(res_body)
content := [][]byte{}
var response Response
err = json.Unmarshal(out, &response)
for _, item := range response.Contents {
item_bytes, _ := json.Marshal(item)
content = append(content, item_bytes)
}
// ----
var meter dmc.Meter
_ = json.Unmarshal(out, &meter)
log.Print(meter.MeterName) //--> ЭЛЕКТРОСНАБЖЕНИЕ
Надеюсь, эта заметка поможет вам избежать подобной ошибки.