Codificación de texto y transformaciones con dart:convert

Codificación de texto y transformaciones con dart:convert

Cuando trabajas con datos que provienen de un socket, un archivo o una petición HTTP, lo que recibes no es texto, sino una secuencia de bytes (List<int>). Para que la computadora pueda interpretar esos números como letras, necesitamos un mapa de traducción llamado codificación (encoding). El estándar actual es UTF-8 [disponible desde Dart 1.0], que es capaz de representar cualquier carácter Unicode de forma eficiente. Si necesitas enviar datos binarios de forma segura a través de una URL, usarás Base64; si esos datos van dentro de una URL, usarás la variante Base64Url.

La lógica de dart:convert es separar la representación física (bytes) de la semántica (caracteres). Usarás estas herramientas cada vez que necesites serializar objetos para guardarlos o transmitir datos por la red. Si eliges el codec equivocado (por ejemplo, intentar usar ascii para un texto con tildes) o si recibes bytes corruptos sin manejar el error, tu programa lanzará una ArgumentError o producirá mojibake (caracteres extraños que rompen la interfaz).

import 'dart:convert';
import 'dart:typed_data';

/// Un codec personalizado que invierte el texto antes de convertirlo a bytes.
/// Implementamos Codec<S, T> donde S es el tipo de origen (String)
/// y T es el tipo de destino (List<int>).
class ReverseCodec extends Codec<String, List<int>> {
  @override
  String decode(List<int> input) {
    // Decodificamos los bytes y luego revertimos el string resultante
    return utf8.decode(input).split('').reversed.join('');
  }

  @override
  List<int> encode(String input) {
    // Revertimos el string primero y luego lo convertimos a bytes UTF-8
    return utf8.encode(input.split('').reversed.join(''));
  }
}

void main() {
  final String mensajeOriginal = "Hola 🚀 <Mundo> & 'Dart'";
  print('Original: $mensajeOriginal');

  // 1. UTF-8: El estándar para casi todo.
  final bytes = utf8.encode(mensajeOriginal);
  print('UTF-8 bytes: $bytes');
  print('Decodificado: ${utf8.decode(bytes)}');

  // 2. Manejo de errores con allowMalformed
  // Simulamos un byte corrupto (0xFF no es una secuencia válida en UTF-8)
  final bytesCorruptos = [72, 111, 32, 244, 255]; 
  final textoSeguro = utf8.decode(bytesCorruptos, allowMalformed: true);
  print('Decodificación con error (allowMalformed: true): $textoSeguro'); 
  // El byte 255 se reemplaza por el carácter de reemplazo Unicode �

  // 3. ASCII vs Latin1
  // ASCII solo soporta 0-127. Si hay tildes, fallará.
  try {
    print(ascii.decode(utf8.encode('Café')));
  } catch (e) {
    print('Error en ASCII: $e'); // Lanza error por la 'é'
  }
  // Latin1 (ISO-8859-1) soporta 0-255.
  print('Latin1: ${latin1.decode(utf8.encode('Café'))}');

  // 4. Base64 y Base64Url
  // Base64 estándar usa '+' y '/', que son problemáticos en URLs.
  final base64String = base64.encode(bytes);
  final base64UrlString = base64Url.encode(bytes);
  print('Base64: $base64String');
  print('Base64Url: $base64UrlString'); // Usa '-' y '_'

  // 5. HTML Escaping
  // Protege contra XSS escapando caracteres especiales.
  final textoEscapado = htmlEscape.convert(mensajeOriginal);
  print('Escapado HTML: $textoEscapado');

  // 6. Stream Transformers
  // Transformar un flujo de bytes directamente en un flujo de strings.
  final streamDeBytes = Stream.fromIterable([
    utf8.encode('Hola '),
    utf8.encode('Mundo!'),
  ]);
  
  print('Stream decodificado:');
  streamDeBytes
      .transform(utf8.decoder) // Transforma Stream<List<int>> a Stream<String>
      .listen((chunk) => print('Chunk del stream: $chunk'));

  // 7. Uso de nuestro Codec personalizado
  final reverseCodec = ReverseCodec();
  final bytesReversos = reverseCodec.encode(mensajeOriginal);
  print('Bytes invertidos: $bytesReversos');
  print('Decodificado inverso: ${reverseCodec.decode(bytesReversos)}');
}

Desglose del ejemplo

En el código anterior, hemos visto cómo utf8.encode transforma el String en una Uint8List (una implementación eficiente de List<int>). Al usar utf8.decode con el parámetro allowMalformed: true, le indicamos a la VM que, en lugar de lanzar una excepción cuando encuentra una secuencia de bytes que no sigue las reglas de UTF-8, inserte el carácter de reemplazo U+FFFD (�). Esto es vital en sistemas de procesamiento de logs o streams de red donde la integridad de los datos no puede garantizarse al 100%.

Fíjate en la diferencia entre los codecs ascii y latin1. ascii es restrictivo y útil para protocolos legacy muy simples, pero para cualquier aplicación moderna con soporte internacional, es un error usarlo. latin1 es un poco más amplio, pero utf8 sigue siendo el rey por su capacidad de escala.

Para la transmisión de datos, base64Url es la opción correcta cuando el string resultante formará parte de una query parameter o un path de una URL, ya que reemplaza los caracteres + y / por - y _ respectivamente, evitando que el servidor interprete mal la ruta.

Cuando trabajamos con Stream, utf8.decoder no es solo una función, es un StreamTransformer. Esto es clave para el rendimiento: permite procesar archivos gigantescos de bytes sin cargar todo el contenido en la memoria RAM, transformando los fragmentos (chunks) conforme llegan.

Finalmente, la implementación de ReverseCodec demuestra cómo extender Codec<S, T>. Al sobreescribir encode y decode, creamos nuestra propia lógica de transformación de datos que puede integrarse perfectamente con cualquier API de Dart que espere un codec, como en la serialización de archivos personalizados.

El error frecuente

Un error común ocurre al intentar usar ascii.decode() sobre bytes que contienen caracteres extendidos.

// ESTO FALLARÁ
final bytes = utf8.encode('Niño'); // El carácter 'ñ' tiene múltiples bytes en UTF-8
final texto = ascii.decode(bytes); // Lanza ArgumentError: Invalid UTF-8 sequence

Si necesitas procesar texto que puede tener caracteres no ASCII, usa siempre utf8.decode(). Solo usa ascii si tienes la certeza absoluta de que los datos cumplen con la norma (caracteres 0-127), como en ciertos encabezados de protocolos de red muy estrictos.

84

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio