1 (изменено: teadrinker, 2018-12-24 14:19:56)

Тема: AHK: Класс для работы с JSON

Класс JSON для работы со строкой в формате JSON

Принципиальное отличие от такого и подобных в том, что для десериализации JSON-строки (т. е. для превращения её в AHK-объект) не используется парсинг. Вместо этого создаётся способом, описанным здесь, JScript-объект, и выполняется метод eval() с JSON-строкой в качестве аргумента; далее JScript-объект превращается в AHK-объект. Такой подход позволяет избежать некоторых ошибок парсинга, а также делает код более компактным.

class JSON
{
   static JS := JSON._GetJScripObject()
   
   Parse(JsonString)  {
      try oJSON := this.JS.("(" JsonString ")")
      catch  {
         MsgBox, Wrong JsonString!
         Return
      }
      Return this._CreateObject(oJSON)
   }
   
   Stringify(obj)  {
      if IsObject( obj )  {
         isArray := true
         for key in obj
            if !( key = A_Index || isArray := false )
               break
            
         for k, v in obj
            str .= ( A_Index = 1 ? "" : "," ) . ( isArray ? "" : this.Stringify(k) . ":" ) . this.Stringify(v)

         return isArray ? "[" str "]" : "{" str "}"
      }
      else if !(obj*1 = "" || RegExMatch(obj, "\s"))
         return obj
      
      for k, v in [["\", "\\"], [A_Tab, "\t"], ["""", "\"""], ["/", "\/"], ["`n", "\n"], ["`r", "\r"], [Chr(12), "\f"], [Chr(08), "\b"]]
         obj := StrReplace( obj, v[1], v[2] )
      
      while RegexMatch( obj, "[^\x20-\x7e]", key )  {
         str := Asc( key )
         val := "\u" . Chr( ( ( str >> 12 ) & 15 ) + ( ( ( str >> 12 ) & 15 ) < 10 ? 48 : 55 ) )
               . Chr( ( ( str >> 8 ) & 15 ) + ( ( ( str >> 8 ) & 15 ) < 10 ? 48 : 55 ) )
               . Chr( ( ( str >> 4 ) & 15 ) + ( ( ( str >> 4 ) & 15 ) < 10 ? 48 : 55 ) )
               . Chr( ( str & 15 ) + ( ( str & 15 ) < 10 ? 48 : 55 ) )
         obj := StrReplace(obj, key, val)
      }
      Return """" obj """"
   }
   
   GetFromUrl(url, contentType := "", userAgent := "", body := "")  {
      ; в случае удачи будет возвращена JSON-строка, в случае ошибки — массив с одним элементом-строкой с описанием ошибки
      try  {
         XmlHttp := ComObjCreate("Microsoft.XmlHttp")
         XmlHttp.Open("GET", url, false)
         ( contentType && XmlHttp.SetRequestHeader("Content-Type", contentType) )
         ( userAgent && XmlHttp.SetRequestHeader("User-Agent", userAgent) )
         XmlHttp.Send(body)
      }
      catch e
         Return ["Error!`n" . e.Message]
      status := XmlHttp.Status
      Return status = 200 ? XmlHttp.ResponseText : ["Error! Status: " . status . ", ResponseText: " . XmlHttp.ResponseText]
   }

   _GetJScripObject()  {
      VarSetCapacity(tmpFile, ((MAX_PATH := 260) - 14) << !!A_IsUnicode, 0)
      DllCall("GetTempFileName", Str, A_Temp, Str, "AHK", UInt, 0, Str, tmpFile)
      
      FileAppend,
      (
      <component>
      <public><method name='eval'/></public>
      <script language='JScript'></script>
      </component>
      ), % tmpFile
      
      JS := ObjBindMethod( ComObjGet("script:" . tmpFile), "eval" )
      FileDelete, % tmpFile
      JSON._AddMethods(JS)
      Return JS
   }

   _AddMethods(ByRef JS)  {
      JScript =
      (
         Object.prototype.GetKeys = function () {
            var keys = []
            for (var k in this)
               if (this.hasOwnProperty(k))
                  keys.push(k)
            return keys
         }
         Object.prototype.IsArray = function () {
            var toStandardString = {}.toString
            return toStandardString.call(this) == '[object Array]'
         }
      )
      JS.("delete ActiveXObject; delete GetObject;")
      JS.(JScript)
   }

   _CreateObject(ObjJS)  {
      res := ObjJS.IsArray()
      if (res = "")
         Return ObjJS
      
      else if (res = -1)  {
         obj := []
         Loop % ObjJS.length
            obj[A_Index] := this._CreateObject(ObjJS[A_Index - 1])
      }
      else if (res = 0)  {
         obj := {}
         keys := ObjJS.GetKeys()
         Loop % keys.length
            k := keys[A_Index - 1], obj[k] := this._CreateObject(ObjJS[k])
      }
      Return obj
   }
}

Пример использования. Методом GetFromUrl() получаем JSON-строку с сайта https://www.coindesk.com/ с курсом биткойна, затем методом Parse() превращаем её в AHK-объект, далее методом Stringify() снова преращаем AHK-объект в JSON-строку (порядок ключей не сохранится, станет в алфавитном порядке).

jsonStr := JSON.GetFromUrl("https://api.coindesk.com/v1/bpi/currentprice.json")

if IsObject(jsonStr)  {
   MsgBox, % jsonStr[1]
   Return
}
if (jsonStr = "")
   Return

MsgBox, 64, JSON, % jsonStr

obj := JSON.Parse(jsonStr)
MsgBox, 64, Bitcoin Price
      , % "upd: " . obj.time.updated . "`n`n"
        . "USD: " . obj.bpi.USD.rate . "`n"
        . "GBP: " . obj.bpi.GBP.rate . "`n"
        . "EUR: " . obj.bpi.EUR.rate
        
MsgBox, 64, JSON From Obj, % jsonStr := JSON.Stringify(obj)

Связанная тема для вопросов.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Skype dmitry_fiveg