Back-End

26 ago, 2013

Serializando e deserializando no Ruby com Marshal (dump e load)

Publicidade

A serialização de objetos nos dias de hoje é essencial. Precisamos serializar hashes no banco; serializar JSON para saídas e entradas de chamadas Ajax e uma pá de necessidades que não seriam possíveis sem serializarmos objetos.

Recapitulando rapidamente, serializar um objeto é transformar sua estrutura (ou estado) que está em memória num formato adequado para transmissão e/ou armazenamento, para que ele possa ser transformado novamente, num segundo momento, em um objeto na memória, com o processo que chamamos de deserialização. Mais rapidamente ainda e de forma grosseira (e espero que didática): é como pegarmos um objeto (uma variável, array, hash…), transformá-la em texto (serialização) e depois transformar novamente em um objeto da memória (deserialização).

O processo se serialização/deserialização também é conhecido como marshalling/unmarshalling edeflating/inflating respectivamente.

Modulo Marshal

O Ruby conta com um módulo específico para serializar/deserializar chamado Marshal. Esse módulo já vem incluido no Ruby. Ele tem basicamente dois métodos:

  • Marshal.dump para serializar um objeto
  • Marshal.load para deserializar um objeto

Um exemplo básico de como isso funciona:

[ruby]hash = {nome: "Léo Hackin"}
hash_serializado = Marshal.dump(hash) # {: nomeI"Léo Hackin:ET
hash_deserializado = Marshal.load(hash_serializado) # {:nome=>"Léo Hackin"}[/ruby]

Em nosso exemplo, temos uma variável chamada hash com um hash dentro, que serializamos com o método Marshal.dump e depois deserializamos com o método Marshal.load. A variável hash_deserializado agora é um clone idêntico, em valores e tipo, ao hash original. Prova disso é que se executarmos uma comparação de tipo e valor entre eles, o resultado é verdadeiro.

[ruby]hash.eql?(hash_deserializado) # retorna TRUE pois os valores e o tipo do objeto são exatamente iguais[/ruby]

Fácil né? É fácil mesmo e sem pegadinhas. Quer dizer, exceto algumas…

Objetos complexos com métodos, associações e por ai vai…

Marshal.dump não consegue fazer o dump de objetos que contenham binding, procedures, métodos de instância e objetos singleton. Se tentar fazer algo assim, uma exceção TypeError é lançada.

A serialização de uma instância de um modelo ActiveRecord com associações serializa/deserializa normalmente. Mas, logicamente, esse objeto não poderá ser usado em outro ambiente que não seja o seu por motivos óbvios: ao ser deserializado, o objetivo virá com o tipo/modelo original e caso você não o tenha no seu ambiente, vai dar pau. Ex.: você serializa um objeto ActiveRecord de um modelo chamado Livro e tenta deserializá-lo em outro ambiente qualquer.

Versão do módulo Marshal

Marshal.load funcionará apenas para objetos serializados com uma versão igual ou menor à do modulo que está tentando carregar.

Ou seja, se você está com a versão 4.8 do módulo Marshal, você conseguirá fazer o load apenas quando o objeto for serializado por essa versão ou menor (4.7, 4.6 …). Por sorte (ou azar) o módulo tem um versionamento diferente do Ruby. Para fazer a checagem da versão serializada, veja os dois primeiros bytes da serialização, como abaixo:

[ruby]

str = Marshal.dump("thing")

str[0].ord #=> 4

str[1].ord #=> 8[/ruby]

Nesse caso a versão é a 4.8.

Problemas de encoding

Em alguns casos você irá serializar objetos que podem conter valores com encodings diferentes do padrão UTF-8, como ASCII-8BIT. Ao invés de interagir por todo o hash ou objeto corrigindo os encodings, você pode forçar a conversão diretamente pelo método dump.

[ruby]

str = Marshal.dump(hash_com_caracteres_ASCII).force_encoding("UTF-8")

str = Marshal.dump(outro_hash_com_caracateres).force_encoding("ISO-8859-1").encode("UTF-8")[/ruby]

Serializando para um arquivo texto

Se você deseja serializar seu objeto para um arquivo texto, tenha cuidado na forma como você vai escrever esse conteúdo. Por algum motivo estranho, algumas vezes em que você joga o conteúdo serializado em um arquivo com o método write do módulo File, isso pode dar pau na hora de deserializar o conteúdo.

Para evitar o problema, escreva o dump no arquivo destino como abaixo.

[ruby]

File.open("arquivo.txt",’w’) do |f|
Marshal.dump(objeto, f)
end[/ruby]

Para fazer a deserialização do objeto armazenado no arquivo:

[ruby]

objeto = File.open("arquivo.txt") do |file|
Marshal.load(file)
end[/ruby]

E é isso!

Referências