Как перекодировать Windows-1251 в UTF8 в Go без страданий

Ниже история одной моей довольно забавной ошибки. Воистину, программист никогда не заскучает; даже имея в руках идеальный инструмент, он сможет применить его неправильно. Что уж говорить об инструментах неидеальных?

Может показаться удивительным, но в просвещённом 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) //--> ЭЛЕКТРОСНАБЖЕНИЕ

Надеюсь, эта заметка поможет вам избежать подобной ошибки.

comments powered by Disqus