从Ruby解析特定的类似JSON的数据(NextSTEP PList)

我正在为第三方API编写客户端,他们以奇怪的格式提供数据。 起初,它可能看起来像JSON,但它不是,我有点困惑我应该如何处理它。

它是一种基于键值的格式(很像JSON)。

  • 键的值由’=’分隔。
  • 键和值包含在双引号内。
  • 字典以'{‘开头,以’}’结尾。
  • 数组以’(’和以’结尾’开头
  • 行以’;’结尾 (数组内容除外)和行尾字符(\ ri think)。
  • 有时,字符串中似乎有unicode(类似于生物危害标志的\ U2623)。

什么可能是这种格式? 我应该使用premade gem来解析它,还是应该构建我自己的解析器?

{ "anArray" = ( "100", "200", "300" ); "aDictionary" = { "aString" = "Something"; }; } 

编辑这种格式似乎是Apple的属性列表,但它不是XML也不是Binary ……这有意义,因为API来自WebObjects Web服务。 我将尝试使用CFPropertyList gem来解析它,如果有更好的解决方案,请告诉我。

编辑2这是一个NextSTEP财产清单 。

这是使用基于StringScanner的自定义解析器的强大答案。 它允许空格是可选的,允许在列表中的最后一项之后使用尾随逗号,并允许在最后一个字典键/值对之后省略分号。 它允许最外面的项目是字典,数组或字符串。 它允许任何类型的合法字符串内容,包括parens和花括号以及\n等转义文本。

看到行动:

 p parse('{ "array" = ( "1", "2", ( "3", "4" ) ); "hash"={ "key"={ "more"="oh}]yes;!"; }; }; }') #=> {"array"=>["1", "2", ["3", "4"]], "hash"=>{"key"=>{"more"=>"oh}]yes;!"}}} puts parse('("Escaped \"Quotes\" Allowed", "And Unicode \u2623 OK")') #=> Escaped "Quotes" Allowed #=> And Unicode ☣ OK 

代码:

 require 'strscan' def parse(str) ss, getstr, getary, getdct = StringScanner.new(str) getvalue = ->{ if ss.scan /\s*\{\s*/ then getdct[] elsif ss.scan /\s*\(\s*/ then getary[] elsif str = getstr[] then str elsif ss.scan /\s*[)}]\s*/ then nil end } getstr = ->{ if str=ss.scan(/\s*"(?:[^"\\]|\\u\d+|\\.)*"\s*/i) eval str.gsub(/([^\\](?:\\\\)*)#(?=[{@$])/,'\1\#') end } getary = ->{ [].tap do |a| while v=getvalue[] a << v ss.scan /\s*,\s*/ end end } getdct = ->{ {}.tap do |h| while key = getstr[] ss.scan /\s*=\s*/ if value=getvalue[] then h[key]=value; ss.scan(/\s*;\s*/) end end end end } getvalue[] end 

作为将来从头开始编译自己的解析器的替代方法,您可能还需要查看Treetop Ruby库。


编辑 :我已经将上面的getstr的实现替换为应该阻止在eval运行任意Ruby代码的实现。 有关更多详细信息,请参阅“Eval一个没有插值的字符串” 。 看到行动:

 @secret = "OH NO!" $secret = "OH NO!" @@secret = "OH NO!" puts parse('"\"#{:NOT&&:very}\" bad. \u262E\n#@secret \\#$secret \\\\#@@secret"') 

这是一个非常快速和肮脏的黑客,它将语法转换为有效的Ruby,然后进行篡改。 请注意,这可能很危险。 更重要的是,这会将键和值中的所有括号转换为方括号。

 def parse(str) eval( str .gsub( /" = (?=[({"])/, '" => ' ) # Dictionary separators become => .gsub( /(?<=[)}"]); (?=[)}"])/, ', ' ) # Dictionary semicolons become , .tr( '()', '[]' ) # ALL parens become square brackets ) end p parse('{ "anArray" = ( "100", "200", "300" ); "aDictionary" = { "aString" = "Something"; }; }') #=> {"anArray"=>["100", "200", "300"], "aDictionary"=>{"aString"=>"Something"}}