2011-10-22 17 views
6

¿Por qué el JSON incorporado de Ruby no deserializa las primitivas JSON simples, y cómo puedo solucionarlo?Deserializar primitivas JSON con la biblioteca Ruby JSON incorporada

irb(main):001:0> require 'json' 
#=> true 

irb(main):002:0> objects = [ {}, [], 42, "", true, nil ] 
#=> [{}, [], 42, "", true] 

irb(main):012:0> objects.each do |o| 
irb(main):013:1* json = o.to_json 
irb(main):014:1> begin 
irb(main):015:2*  p JSON.parse(json) 
irb(main):016:2> rescue Exception => e 
irb(main):017:2>  puts "Error parsing #{json.inspect}: #{e}" 
irb(main):018:2> end 
irb(main):019:1> end 
{} 
[] 
Error parsing "42": 706: unexpected token at '42' 
Error parsing "\"\"": 706: unexpected token at '""' 
Error parsing "true": 706: unexpected token at 'true' 
Error parsing "null": 706: unexpected token at 'null' 
#=> [{}, [], 42, "", true, nil] 

irb(main):020:0> RUBY_DESCRIPTION 
#=> "ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-darwin10.7.0]" 
irb(main):022:0> JSON::VERSION 
#=> "1.4.2" 

Respuesta

9

RFC 4627: The application/json Media Type for JavaScript Object Notation (JSON) tiene esto que decir:

2. JSON Grammar 

    A JSON text is a sequence of tokens. The set of tokens includes six 
    structural characters, strings, numbers, and three literal names. 

    A JSON text is a serialized object or array. 

     JSON-text = object/array 

[...] 

2.1. Values 

    A JSON value MUST be an object, array, number, or string, or one of 
    the following three literal names: 

     false null true 

Si llama to_json en sus seis objetos de la muestra, obtenemos esto:

>> objects = [ {}, [], 42, "", true, nil ] 
>> objects.map { |o| puts o.to_json } 
{} 
[] 
42 
"" 
true 
null 

Así que el primer y segundo son válidos textos JSON mientras que los últimos cuatro no son válidos Textos JSON aunque son válidos valores JSON.

JSON.parse quiere lo que llama un documento JSON:

analizar la fuente JSON documento en una estructura de datos Ruby y devolverlo.

documento Quizás JSON es el término de la biblioteca por lo RFC 4627 llama a un texto JSON . Si es así, generar una excepción es una respuesta razonable a una entrada no válida.

Si envuelve a la fuerza y ​​desenvolver todo:

objects.each do |o| 
    json = o.to_json 
    begin 
     json_text = '[' + json + ']' 
     p JSON.parse(json_text)[0] 
    rescue Exception => e 
     puts "Error parsing #{json.inspect}: #{e}"  
    end  
end 

Y como se nota en su comentario, utiliza una matriz como la envoltura es mejor que un objeto en caso de que la persona que llama desea utilizar la opción :symbolize_names. Envolver de este modo significa que siempre estará alimentando JSON.parse un texto JSON y todo debería estar bien.

+1

Sugiero usar un contenedor de matriz en lugar de un contenedor de objetos, en caso de que el usuario quiera pasar 'symbolize_names: true' a' JSON.parse'. Con la matriz, el método para desenvolver el resultado no se ve afectado. – Phrogz

+0

BTW, gran respuesta, citando el RFC. Mientras que el argumento que presenta lo hace parecer razonable, no es (en mi humilde opinión) razonable para 'JSON.parse (o.to_json)!= o' para valores simplemente serializables. – Phrogz

+0

@Phrogz: Excelente punto, he parchado mi respuesta en consecuencia. Estoy de acuerdo en que no analizar los valores no-objeto/matriz es un poco pedante, especialmente en el mundo DWIM Ruby y especialmente por no haber documentado dicho comportamiento. –

1

Parece que el analizador JSON incorporado falla intencionalmente en cualquier cosa que no sean objetos y matrices. Mi solución actual es la siguiente:

# Work around a flaw in Ruby's built-in JSON parser 
# not accepting anything but an object or array at the root level. 
module JSON 
    def self.parse_any(str,opts={}) 
    parse("[#{str}]",opts).first 
    end 
end 
+0

Un buen juego, pero creo que envolver y desenvolver sería más seguro que el formato adivinar. –

+0

@muistooshort Un punto excelente; es probable que casi no haya golpe de rendimiento para envolver y desenvolver siempre. ¿Alguna razón por la que prefiere envolver en un objeto en comparación con una matriz? – Phrogz

+0

Puede valer la pena realizar algunas evaluaciones comparativas, pero dudo que pueda detectar la diferencia sin utilizar pequeños bits de JSON y decenas de miles de iteraciones. –

-1

Creo que tienes razón ... si se trata de un error o no, hay cierta lógica poco firme sucediendo con la aplicación. Si puede analizar matrices y hashes, debería poder analizar todo lo demás.

Como JSON.parse parece orientado para objetos y matrices, intentaría pasar sus datos de una de esas formas si puede, y si no puede, quédese con la solución alternativa que tiene.