Thread JSON und utf8 (25 answers)
Opened by rosti at 2018-05-06 18:58

haj
 2018-05-07 08:40
#188349 #188349
User since
2015-01-07
531 Artikel
BenutzerIn

user image
Da muss ich doch nochmal nachhaken. Ich habe schon oft gehört / gelesen, dass irgendeine Software beim Encoding Mist mache, und in den meisten Fällen liegt der eigentliche Fehler aber bei irrtümlichen Annahmen, zu denen die Encoding-Welt reichlich Anlass bietet. Das wird jetzt etwas länglich.

Die erste oft getroffene Annahme ist, dass use utf8 etwas damit zu tun habe, wie Perl seine I/O macht. Das ist aber falsch: Mit dieser Angabe sagt man dem Perl-Parser, dass diese Quelldatei in UTF-8 codiert daherkommt (https://perldoc.perl.org/utf8.html). Wer seine I/O per Default anders codiert haben will, kann das mit use open ':encoding(UTF-8)' machen - aber wieder falsch wäre die Annahme, das wirke auch auf STDOUT: Das wirkt nur auf Dateien, die man selber unter dem gleichen Scope aufmacht.

Quote
Ein use utf8 in das Script einzubauen ist da natürlich blödsinnig.
- nein, das ist nicht blödsinning, sondern entweder falsch oder richtig, je nachdem, wie die Datei abgespeichert wurde.

In dem Quelltext steht z.B. das Literal "Ihre Lösung war richtig!". Welche Byte-Sequenz für das 'ö' auf die Platte kommt, hängt nun davon ab, in welchem Encoding der Editor das wegschreibt. Und weil inzwischen 2018 ist, nehmen wir mal an, es sei UTF-8. Der Perl-Parser trifft aber keine Annahmen: Wenn nix drinsteht, parst er den Text in seinem Default-Encoding, und das ist so in etwa beinuhe ISO-LATIN-1. Das "ö" in einer UTF-8-Quelldatei sind zwei Bytes, also in diesem Encoding zwei Characters. Zum Ausprobieren:

Code: (dl )
1
2
my $char = "ö";
print length $char,"\n";


Speichert man diesen Quellcode als UTF-8, dann ist die Länge 2, speichert man ihn als ISO-LATIN-1, dann ist die Länge 1.

Wer will, kann's auch als Einzeiler in sein Terminal klopfen. Mal unter Linux, mal unter Windows cmd.exe (mit " anstelle der ' bei cmd.exe), gerne auch PowerShell oder Cygwin Bash:

Code: (dl )
perl -e '$l=q(ö); print length $l,qq(\n)'


Unter heutigem Linux ist das 2, weil die heutigen Shells/Terminals UTF-8 als Encoding eingestellt haben. Unter Windows cmd.exe eine 1.

Im Gegenbeispiel speichere man folgende Datei unter ISO-LATIN-1 ab und verfüttere sie an Perl:
Code: (dl )
1
2
3
use utf8;
my $char = "ö";
print length $char,"\n";


Da kommt dann von Perl zu Recht die Beschwerde Malformed UTF-8 character (1 byte, need 4, after start byte 0xf6).

Quote
Aber hier in diesem Fall gehen interne Angelegenheiten nach STDOUT was nicht sein darf.
Was nach STDOUT geht, wird von Perls I/O-Layer so aus interner Darstellung in Bytes umgewandelt, wie man es ihm angeschafft hat. Und wenn man nichts explizit anweist, dann wirkt der Default-I/O-Layer, und der ist nicht UTF-8. Wenn man ein UTF-8-codiertes "ö" im Default-Encoding einliest und hinterher im Default-Encoding wieder ausgibt, dann liefert das ein UTF-8-codiertes "ö", weil das Default-Encoding Bytes liest und Bytes rausschreibt. Für Perl intern sind das zwei Bytes - und auch zwei Characters. Deswegen gehen solche Konstruktionen erst dann für alle sichtbar in die Hosen, wenn gleichzeitig "richtige" UTF-8-Characters verarbeitet werden sollen.

Quote
Handle kennen nämlich keine Kodierung, sondern nur noch Bytes.
Absolut richtig. Das fängt aber schon bei der Quelldatei an, siehe oben.

Und deswegen macht hier auch JSON.pm keinen Mist, sondern tut genau das, was es soll. Mit utf8(0) läßt es die Strings, wie sie sind, und man muss es selbst im UTF-8-Encoding rausschreiben. Mit utf8(1) wandelt es einen String in UTF-8-codierte Bytes um, und man muss es im Byte-Encoding rausschreiben. Wenn der String aber schon vorher vermurkst war, weil ein UTF-8-codiertes "ö" im Default-Encoding eingelesen wurde, dann kann JSON das nicht reparieren.

View full thread JSON und utf8