1 (изменено: Phoenixxx_Czar, 2021-05-06 19:15:12)

Тема: AHK: Помогите переписать JSON_Stringify

Добрейшего всем, хочу реализовать подобие JSON.stringify() у JavaScript. Там есть такая крутая возможность - указать space, которое будет делать отступы.

+ Это теперь работает

Есть JSON строка:

{"Accept-Ranges": ["bytes"], "Alt-Svc": ["quic=\":443\"", "ma=2592000", "v=\"43,46\", h3-Q043=\":443\"", "ma=2592000, h3-Q046=\":443\"", "ma=2592000, h3-Q050=\":443\"", "ma=2592000, h3-25=\":443\"", "ma=2592000, h3-27=\":443\"", "ma=2592000"], "Connection": ["keep-alive"], "Content-Length": ["4433"], "Content-Type": ["text/html"], "Date": ["Thu, 06 May 2021 06:16:13 GMT"], "Keep-Alive": ["timeout=60"], "Last-Modified": ["Thu, 04 Mar 2021 14:43:58 GMT"], "Server": ["ddos-guard"], "Set-Cookie": ["__ddg1=xBGnCJjM7HmST7hYgF6j", "Domain=.test.com", "HttpOnly", "Path=/", "Expires=Fri, 06-May-2022 06:16:13 GMT"], "Vary": ["User-Agent"], "X-Turbo-Charged-By": ["LiteSpeed"]}

И нужно получить вот такое:

{
	"Accept-Ranges":
	[
		"bytes"
	],
	"Alt-Svc":
	[
		"quic=\":443\"",
		"ma=2592000",
		"v=\"43,46\", h3-Q043=\":443\"",
		"ma=2592000, h3-Q046=\":443\"",
		"ma=2592000, h3-Q050=\":443\"",
		"ma=2592000, h3-25=\":443\"",
		"ma=2592000, h3-27=\":443\"",
		"ma=2592000"
	],
	"Connection":
	[
		"keep-alive"
	],
	"Content-Length":
	[
		"4433"
	],
	"Content-Type":
	[
		"text/html"
	],
	"Date":
	[
		"Thu, 06 May 2021 06:16:13 GMT"
	],
	"Keep-Alive":
	[
		"timeout=60"
	],
	"Last-Modified":
	[
		"Thu, 04 Mar 2021 14:43:58 GMT"
	],
	"Server":
	[
		"ddos-guard"
	],
	"Set-Cookie":
	[
		"__ddg1=xBGnCJjM7HmST7hYgF6j",
		"Domain=.test.com",
		"HttpOnly",
		"Path=/",
		"Expires=Fri, 06-May-2022 06:16:13 GMT"
	],
	"Vary":
	[
		"User-Agent"
	],
	"X-Turbo-Charged-By":
	[
		"LiteSpeed"
	]
}

Мои попытки:

class JSON
{
	Decode(jsonStr)
	{
		SC := ComObjCreate("ScriptControl")
		SC.Language := "JScript"
		ComObjError(false)

		jsCode =
		(
		function arrangeForAhkTraversing(obj){
			if(obj instanceof Array){
				for(var i=0 ; i<obj.length ; ++i)
					obj[i] = arrangeForAhkTraversing(obj[i]) ;
				return ['array',obj] ;
			}else if(obj instanceof Object){
				var keys = [], values = [] ;
				for(var key in obj){
					keys.push(key) ;
					values.push(arrangeForAhkTraversing(obj[key])) ;
				}
				return ['object',[keys,values]] ;
			}else
				return [typeof obj,obj] ;
		}
		)

		SC.ExecuteStatement(jsCode "; obj=" jsonStr)
		return this.convertJScriptObjToAhks( SC.Eval("arrangeForAhkTraversing(obj)") )
	}

	convertJScriptObjToAhks(jsObj)
	{
		if (jsObj[0]="object")
		{
			obj := {}, keys := jsObj[1][0], values := jsObj[1][1]
			loop % keys.length
				obj[keys[A_INDEX-1]] := this.convertJScriptObjToAhks( values[A_INDEX-1] )
			return obj
		}
		else if (jsObj[0]="array")
		{
			array := []
			loop % jsObj[1].length
				array.insert(this.convertJScriptObjToAhks( jsObj[1][A_INDEX-1] ))
			return array
		}
		else
			return jsObj[1]
	}

	Stringify(value, space := 0, _indent := 1)
	{
		_space := space
		if (space ~= "^\d+$")
		{
			_space := strRepeat(A_Space, space)
		}

		str := ""
		array := true
		for k in value
		{
			if (k == A_Index)
			{
				continue
			}

			array := false
			break
		}

		indent := _indent
		for a, b in value
		{
			if (space)
			{
				str .= strRepeat(_space, indent)
			}

			str .= (array ? "" : """" a """: ")

			if (IsObject(b))
			{
				if (space)
				{
					str := RTrim(str, " ") "`n" strRepeat(_space, indent)
				}

				str .= this.Stringify(b, space, indent + 1)
			}
			else
			{
				str .= """" StrReplace(b, """", "\""") """"
			}

			str .= "," (space ? "`n" : " ")
		}
		str := RTrim(str, " ,`n")
		str := RegExReplace(str, "`n([\s]+)?`n", "`n")

		indent--
		out := (array ? "[" : "{") (space ? "`n" : "") str (space ? "`n" : "")
		out .= (space ? strRepeat(_space, indent) : "") (array ? "]" : "}")
		return out
	}
}

Доп. функции которые использовались в функциии:


strRepeat(str, count)
{
	return StrReplace(Format("{:" count "}", ""), " ", str)
}

objectMerge(target, sources*)
{
	target := clone(target)

	for i, arr in sources
	{
		for key, value in arr
		{
			target[key] := (IsObject(value) && !value.base.IsArray) ? ObjectMerge(value, target[key]) : value
		}
	}

	return target
}

clone(obj)
{
	cloned := obj.clone()
	for i, key in obj
	{
		if IsObject(key)
		{
			cloned[i] := clone(key)
		}
	}
	return cloned
}

Использование:


jsonString = {"Accept-Ranges": ["bytes"], "Alt-Svc": ["quic=\":443\"", "ma=2592000", "v=\"43,46\", h3-Q043=\":443\"", "ma=2592000, h3-Q046=\":443\"", "ma=2592000, h3-Q050=\":443\"", "ma=2592000, h3-25=\":443\"", "ma=2592000, h3-27=\":443\"", "ma=2592000"], "Connection": ["keep-alive"], "Content-Length": ["4433"], "Content-Type": ["text/html"], "Date": ["Thu, 06 May 2021 06:16:13 GMT"], "Keep-Alive": ["timeout=60"], "Last-Modified": ["Thu, 04 Mar 2021 14:43:58 GMT"], "Server": ["ddos-guard"], "Set-Cookie": ["__ddg1=xBGnCJjM7HmST7hYgF6j", "Domain=.test.com", "HttpOnly", "Path=/", "Expires=Fri, 06-May-2022 06:16:13 GMT"], "Vary": ["User-Agent"], "X-Turbo-Charged-By": ["LiteSpeed"]}

test1 := JSON.Decode(jsonString)
test2 := {key1: 1, key2: 2, key3: ["val1", "val2"], tupac: ["Shakur", {"test": 1, tupac: ["Shakur", {"test": 1}]}]}

msgbox, % JSON.Stringify(test1) ; (1) Все в одну строку
msgbox, % JSON.Stringify(test1, "`t") ; (2) С табуляцией
msgbox, % JSON.Stringify(test1, 2) ; (3) С двумя пробелами

msgbox, % JSON.Stringify(test2) ; ;(4) Все в одну строку
msgbox, % JSON.Stringify(test2, "`t") ; (5) С табуляцией
msgbox, % JSON.Stringify(test2, 2) ; (6) С двумя пробелами
+ Что мы получаем
+ (1)

{"Accept-Ranges": ["bytes"], "Alt-Svc": ["quic=\":443\"", "ma=2592000", "v=\"43,46\", h3-Q043=\":443\"", "ma=2592000, h3-Q046=\":443\"", "ma=2592000, h3-Q050=\":443\"", "ma=2592000, h3-25=\":443\"", "ma=2592000, h3-27=\":443\"", "ma=2592000"], "Connection": ["keep-alive"], "Content-Length": ["4433"], "Content-Type": ["text/html"], "Date": ["Thu, 06 May 2021 06:16:13 GMT"], "Keep-Alive": ["timeout=60"], "Last-Modified": ["Thu, 04 Mar 2021 14:43:58 GMT"], "Server": ["ddos-guard"], "Set-Cookie": ["__ddg1=xBGnCJjM7HmST7hYgF6j", "Domain=.test.com", "HttpOnly", "Path=/", "Expires=Fri, 06-May-2022 06:16:13 GMT"], "Vary": ["User-Agent"], "X-Turbo-Charged-By": ["LiteSpeed"]}
+ (2)

{
	"Accept-Ranges":
	[
		"bytes"
	],
	"Alt-Svc":
	[
		"quic=\":443\"",
		"ma=2592000",
		"v=\"43,46\", h3-Q043=\":443\"",
		"ma=2592000, h3-Q046=\":443\"",
		"ma=2592000, h3-Q050=\":443\"",
		"ma=2592000, h3-25=\":443\"",
		"ma=2592000, h3-27=\":443\"",
		"ma=2592000"
	],
	"Connection":
	[
		"keep-alive"
	],
	"Content-Length":
	[
		"4433"
	],
	"Content-Type":
	[
		"text/html"
	],
	"Date":
	[
		"Thu, 06 May 2021 06:16:13 GMT"
	],
	"Keep-Alive":
	[
		"timeout=60"
	],
	"Last-Modified":
	[
		"Thu, 04 Mar 2021 14:43:58 GMT"
	],
	"Server":
	[
		"ddos-guard"
	],
	"Set-Cookie":
	[
		"__ddg1=xBGnCJjM7HmST7hYgF6j",
		"Domain=.test.com",
		"HttpOnly",
		"Path=/",
		"Expires=Fri, 06-May-2022 06:16:13 GMT"
	],
	"Vary":
	[
		"User-Agent"
	],
	"X-Turbo-Charged-By":
	[
		"LiteSpeed"
	]
}
+ (3)

{
  "Accept-Ranges":
  [
    "bytes"
  ],
  "Alt-Svc":
  [
    "quic=\":443\"",
    "ma=2592000",
    "v=\"43,46\", h3-Q043=\":443\"",
    "ma=2592000, h3-Q046=\":443\"",
    "ma=2592000, h3-Q050=\":443\"",
    "ma=2592000, h3-25=\":443\"",
    "ma=2592000, h3-27=\":443\"",
    "ma=2592000"
  ],
  "Connection":
  [
    "keep-alive"
  ],
  "Content-Length":
  [
    "4433"
  ],
  "Content-Type":
  [
    "text/html"
  ],
  "Date":
  [
    "Thu, 06 May 2021 06:16:13 GMT"
  ],
  "Keep-Alive":
  [
    "timeout=60"
  ],
  "Last-Modified":
  [
    "Thu, 04 Mar 2021 14:43:58 GMT"
  ],
  "Server":
  [
    "ddos-guard"
  ],
  "Set-Cookie":
  [
    "__ddg1=xBGnCJjM7HmST7hYgF6j",
    "Domain=.test.com",
    "HttpOnly",
    "Path=/",
    "Expires=Fri, 06-May-2022 06:16:13 GMT"
  ],
  "Vary":
  [
    "User-Agent"
  ],
  "X-Turbo-Charged-By":
  [
    "LiteSpeed"
  ]
}
+ (4)

{"key1": "1", "key2": "2", "key3": ["val1", "val2"], "tupac": ["Shakur", {"test": "1", "tupac": ["Shakur", {"test": "1"}]}]}
+ (5)

{
	"key1": "1",
	"key2": "2",
	"key3":
	[
		"val1",
		"val2"
	],
	"tupac":
	[
		"Shakur",
		{
			"test": "1",
			"tupac":
			[
				"Shakur",
				{
					"test": "1"
				}
			]
		}
	]
}
+ (6)

{
  "key1": "1",
  "key2": "2",
  "key3":
  [
    "val1",
    "val2"
  ],
  "tupac":
  [
    "Shakur",
    {
      "test": "1",
      "tupac":
      [
        "Shakur",
        {
          "test": "1"
        }
      ]
    }
  ]
}

Мне не нравится мой код метод Stringify класса JSON, слишком все костыльно сделано, что можно придумать?

2

Re: AHK: Помогите переписать JSON_Stringify

json = {"Accept-Ranges": ["bytes"], "Alt-Svc": ["quic=\":443\"", "ma=2592000", "v=\"43,46\", h3-Q043=\":443\"", "ma=2592000, h3-Q046=\":443\"", "ma=2592000, h3-Q050=\":443\"", "ma=2592000, h3-25=\":443\"", "ma=2592000, h3-27=\":443\"", "ma=2592000"], "Connection": ["keep-alive"], "Content-Length": ["4433"], "Content-Type": ["text/html"], "Date": ["Thu, 06 May 2021 06:16:13 GMT"], "Keep-Alive": ["timeout=60"], "Last-Modified": ["Thu, 04 Mar 2021 14:43:58 GMT"], "Server": ["ddos-guard"], "Set-Cookie": ["__ddg1=xBGnCJjM7HmST7hYgF6j", "Domain=.test.com", "HttpOnly", "Path=/", "Expires=Fri, 06-May-2022 06:16:13 GMT"], "Vary": ["User-Agent"], "X-Turbo-Charged-By": ["LiteSpeed"]}

MsgBox, % JsonAddIndents(json)

JsonAddIndents(json, indent := "   ") {
   static doc := ComObjCreate("htmlfile")
        , _ := doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
        , JS := doc.parentWindow
   Return JS.eval("JSON.stringify(" . json . ",'','" . indent . "')")
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

3 (изменено: Phoenixxx_Czar, 2021-05-06 15:37:15)

Re: AHK: Помогите переписать JSON_Stringify

Данный вариант не совсем подходит.
1. JS функция не всегда переносило [ { на новые строки.
2. Если я не ошибаюсь, то с "htmlfile" были проблемы, когда оно не так символы переводило/теряло (я не помню почему именно я ушел от данного решения).
UPD:
3. Данный код вывел пустоту.
UPD:
У вас на вход идет JSON строка, что приносит неудобности (JS функция принимала объект/массив).

4

Re: AHK: Помогите переписать JSON_Stringify

Эта функция парсит, как нативная функция JSON.stringify, так что с переносами, как есть.
У меня никогда не было проблем с htmlfile, не знаю, в чём может быть дело, этот и подобный код многократно постился на AHK-форуме, никто не сообщал о проблемах.
Если данный код выводит пустоту, возможно какие-то проблемы с настройками IE.
Для объекта есть класс JSON, в коллекции.
Или вот упрощённый вариант:

MsgBox, % AhkToJSON({ key1: "value", key2: {ключ1: "знчение", ключ2: ["one", "two", "three"]} }, "   ")

AhkToJSON(obj, indent := "") {
   static doc := ComObjCreate("htmlfile")
        , _ := doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
        , JS := doc.parentWindow

   if indent|1 {
      if IsObject( obj ) {
         isArray := true
         for key in obj {
            if IsObject(key)
               throw Exception("Invalid key")
            if !( key = A_Index || isArray := false )
               break
         }
         for k, v in obj
            str .= ( A_Index = 1 ? "" : "," ) . ( isArray ? "" : """" . k . """:" ) . %A_ThisFunc%(v, true)

         Return isArray ? "[" str "]" : "{" str "}"
      }
      else if !(obj*1 = "" || RegExMatch(obj, "^-?0|\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] )

      Return """" obj """"
   }
   sObj := %A_ThisFunc%(obj, true)
   Return JS.eval("JSON.stringify(" . sObj . ",'','" . indent . "')")
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

5

Re: AHK: Помогите переписать JSON_Stringify

teadrinker пишет:

Эта функция парсит, как нативная функция JSON.stringify, так что с переносами, как есть.

Тогда же это не решение вопроса.

teadrinker пишет:

Если данный код выводит пустоту, какие-то проблемы с настройками IE.

Код выдал пустоту ибо я передал AHK объект.

6

Re: AHK: Помогите переписать JSON_Stringify

Phoenixxx_Czar пишет:

JS функция принимала объект/массив

А что это за JS функция?

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

7

Re: AHK: Помогите переписать JSON_Stringify

JSON.stringify в JS первым параметром принимает объект/массив (не JSON строку).

8

Re: AHK: Помогите переписать JSON_Stringify

Для объекта последний код, который я запостил.

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

9

Re: AHK: Помогите переписать JSON_Stringify

Это все равно не решит проблему с тем, что оно не переносит "{" и "[" на новую строку.

10

Re: AHK: Помогите переписать JSON_Stringify

А зачем нужно переносить?

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

11

Re: AHK: Помогите переписать JSON_Stringify

Ну.. Мне так хочется, по моему этого достаточно).
А вообще я не люблю когда "{" "[" находятся на одной строке с "условием", по этому хочу с переносом.

12

Re: AHK: Помогите переписать JSON_Stringify

Ясно. Ну, мне с вашим вариантом лень разбираться. Посмотрите на мой последний, возможно что-то оттуда можно использовать.

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

13

Re: AHK: Помогите переписать JSON_Stringify

Думаю только считать текущий indent и удалять { [ и добавлять на новую строку с учетом этого indent.. Думаете хороший вариант?

Да и мой, это по сути переделанный ваш, который объект/массив переводит в строку, только со "своей" системой отступов.

14 (изменено: Phoenixxx_Czar, 2021-05-06 16:53:48)

Re: AHK: Помогите переписать JSON_Stringify

Что насчет такого?

JsonAddIndents(json, space := 0)
{
	static doc := ComObjCreate("htmlfile")
		, _ := doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
		, JS := doc.parentWindow

	if (space && space ~= "^\d+$")
	{
		space := strRepeat(A_Space, space)
	}

	res := JS.eval("JSON.stringify(" . json . ",null,'" . space . "')")
	out := ""
	loop, parse, res, `n
	{
		if (A_LoopField ~= "\s+\[$" && RegExMatch(A_LoopField, "O)^(`t+)", _out))
		{
			out .= RegExReplace(A_LoopField, "O)\s+\[$", "`n" strRepeat(space, _out.len) "[`n")
		}
		else
		{
			out .= A_LoopField "`n"
		}
	}
	return out
}

15

Re: AHK: Помогите переписать JSON_Stringify

В смысле, вы же объект вроде на вход хотели?

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

16

Re: AHK: Помогите переписать JSON_Stringify

Я планирую переписать JSON.Stringify в библиотеке на свою, тот метод (который делает в строку) придется тогда вынести под другим названием (например с _ в начале) и использовать его внутри этого.

17 (изменено: teadrinker, 2021-05-06 17:12:26)

Re: AHK: Помогите переписать JSON_Stringify

В общем, можно просто обработать json регуляркой, чтобы перенести открывающие скобки на новую строку, например:

json =
(
{
   "firstName": "Иван",
   "lastName": "Иванов",
   "address": {
       "streetAddress": "Московское ш., 101, кв.101",
       "city": "Ленинград",
       "postalCode": 101101
   },
   "phoneNumbers": [
       "812 123-1234",
       "916 123-4567"
   ]
}
)
MsgBox, % json := RegExReplace(json, "m`a)^(\h*)(\V*\S\V*?)\h*(\[|\{)\h*$", "$1$2`r`n$1$3")
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

18 (изменено: Phoenixxx_Czar, 2021-05-06 17:16:10)

Re: AHK: Помогите переписать JSON_Stringify

Что-то такое вышло:

class JSON
{
	Parse(jsonStr)
	{
		; Create the COM object we'll use to decode the string
		SC := ComObjCreate("ScriptControl")
		SC.Language := "JScript"
		ComObjError(false)

		; This next part is a JScript (similar to JavaScript), not AHK, just FYI
		; This does the actual parsing
		jsCode =
		(
		function arrangeForAhkTraversing(obj){
			if(obj instanceof Array){
				for(var i=0 ; i<obj.length ; ++i)
					obj[i] = arrangeForAhkTraversing(obj[i]) ;
				return ['array',obj] ;
			}else if(obj instanceof Object){
				var keys = [], values = [] ;
				for(var key in obj){
					keys.push(key) ;
					values.push(arrangeForAhkTraversing(obj[key])) ;
				}
				return ['object',[keys,values]] ;
			}else
				return [typeof obj,obj] ;
		}
		)

		; Decode the JSON into an array (call the JS function above) using the COM object, then return an AHK object
		SC.ExecuteStatement(jsCode "; obj=" jsonStr)
		return this.convertJScriptObjToAhks( SC.Eval("arrangeForAhkTraversing(obj)") )
	}

	convertJScriptObjToAhks(jsObj)
	{
		if(jsObj[0]="object")
		{
			obj := {}, keys := jsObj[1][0], values := jsObj[1][1]
			loop % keys.length
				obj[keys[A_INDEX-1]] := this.convertJScriptObjToAhks( values[A_INDEX-1] )
			return obj
		}
		else if (jsObj[0]="array")
		{
			array := []
			loop % jsObj[1].length
				array.insert(this.convertJScriptObjToAhks( jsObj[1][A_INDEX-1] ))
			return array
		}
		else
			return jsObj[1]
	}

	Stringify(value, space := 0)
	{
		static doc := ComObjCreate("htmlfile")
			, _ := doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
			, JS := doc.parentWindow

		if (space && space ~= "^\d+$")
		{
			space := strRepeat(A_Space, space)
		}

		obj := this._stringify(value)
		if (!space)
		{
			return obj
		}

		res := JS.eval("JSON.stringify(" obj ", null, " (space ? "'" space "'" : 0) ")")
		return RegExReplace(res, "m`a)^(\h*)(\V*\S\V*)(\[|\{)\h*$", "$1$2`r`n$1$3")
	}

	_stringify(obj)
	{
		str := ""
		array := true
		for k in obj
		{
			if (k == A_Index)
				continue
			array := false
			break
		}
		for a, b in obj
			str .= (array ? "" : """" a """: ") . (IsObject(b) ? this._stringify(b) : """" StrReplace(b, """", "\""") """") . ", "
		str := RTrim(str, " ,")
		return (array ? "[" str "]" : "{" str "}")
	}
}

19

Re: AHK: Помогите переписать JSON_Stringify

ScriptControl есть только в 32-битном исполнении, не будет работать на AHK 64-bit.

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

20 (изменено: Phoenixxx_Czar, 2021-05-06 18:34:45)

Re: AHK: Помогите переписать JSON_Stringify

У меня все скрипты скомпилированы в Ansi-32, все будет хорошо?
Хотя глупый вопрос, я этот класс использую долгое время ибо тот что на форуме, ломает вроде русские буквы, а другой какой-то вариант ломал данные (не знаю почему). По этому и остановился на этом варианте.

UPD:
Что-то страшно мне стало использовать вариант с htmlfile.. Судя из прошлого моего. Похоже придется вернуться к изначальному варианту..

21

Re: AHK: Помогите переписать JSON_Stringify

Phoenixxx_Czar пишет:

У меня все скрипты скомпилированы в Ansi-32, все будет хорошо?

Не знаю, ANSI сохранён только из соображений обратной совместимости. Отсюда и проблемы с русскими буквами. Сейчас нормальный вариант — это Unicode.

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

22 (изменено: Phoenixxx_Czar, 2021-05-06 19:05:32)

Re: AHK: Помогите переписать JSON_Stringify

К сожалению частые проекты все еще связаны с игрой, где используется ANSI кодировка + компилятор сделан на ANSI (+ заморозил себя на 1.1.25 версии ахк).

OFF: Я похоже сломал верстку чем-то, но не понимаю чем.. Спойлер в спойлере?
UPD OFF: Спойлер не закрыл.. На теги "code" форум ругается, а на спойлеры нет? Багуля))

23

Re: AHK: Помогите переписать JSON_Stringify

Phoenixxx_Czar пишет:

На теги "code" форум ругается, а на спойлеры нет? Багуля))

Да, известный баг со спойлерами.

24

Re: AHK: Помогите переписать JSON_Stringify

Нашел ошибку, при которой:

[{test: 1}]

Превращается в:


[

	{
		"test": "1"
	}
]

А ожидается:


[
	{
		"test": "1"
	}
]

Использование:

msgbox, % JSON.Stringify([{"test": 1}], "`t")
class JSON
{
	Stringify(value, space := 0, _indent := 1)
	{
		_space := space
		if (space ~= "^\d+$")
		{
			_space := strRepeat(A_Space, space)
		}

		str := ""
		array := true
		for k in value
		{
			if (k == A_Index)
			{
				continue
			}

			array := false
			break
		}

		indent := _indent
		for a, b in value
		{
			if (space)
			{
				str .= strRepeat(_space, indent)
			}

			str .= (array ? "" : """" a """: ")

			if (IsObject(b))
			{
				if (space)
				{
					str := RTrim(str, " ") "`n" strRepeat(_space, indent)
				}

				str .= this.Stringify(b, space, indent + 1)
			}
			else
			{
				str .= """" StrReplace(b, """", "\""") """"
			}

			str .= "," (space ? "`n" : " ")
		}
		str := RTrim(str, " ,`n")
		str := RegExReplace(str, "`n([\s]+)?`n", "`n")

		indent--
		out := (array ? "[" : "{") (space ? "`n" : "") str (space ? "`n" : "")
		out .= (space ? strRepeat(_space, indent) : "") (array ? "]" : "}")
		return out
	}
}

strRepeat(str, count)
{
	return StrReplace(Format("{:" count "}", ""), " ", str)
}

Помогите исправить.

25

Re: AHK: Помогите переписать JSON_Stringify

Phoenixxx_Czar пишет:

Превращается в:
А ожидается:

А в чём разница между первым и вторым? Я не вижу. И зачем 1 в кавычках?

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

26

Re: AHK: Помогите переписать JSON_Stringify

teadrinker пишет:

А в чём разница между первым и вторым?

Пустая строка после "[".

teadrinker пишет:

И зачем 1 в кавычках?

Кхм.. Оно любое значение берет в кавычки.

27

Re: AHK: Помогите переписать JSON_Stringify

json := [{test: 1}]
MsgBox, % AhkToJSON(json, "`t")

AhkToJSON(obj, indent := "") {
   static Doc, JS
   if !Doc {
      Doc := ComObjCreate("htmlfile")
      Doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
      JS := Doc.parentWindow
      ( Doc.documentMode < 9 && JS.execScript() )
   }
   if indent|1 {
      if IsObject( obj ) {
         isArray := true
         for key in obj {
            if IsObject(key)
               throw Exception("Invalid key")
            if !( key = A_Index || isArray := false )
               break
         }
         for k, v in obj
            str .= ( A_Index = 1 ? "" : "," ) . ( isArray ? "" : """" . k . """:" ) . %A_ThisFunc%(v, true)

         Return isArray ? "[" str "]" : "{" str "}"
      }
      else if !(obj*1 = "" || RegExMatch(obj, "^-?0|\s"))
         Return obj
      
      for k, v in [["\", "\\"], [A_Tab, "\t"], ["""", "\"""], ["/", "\/"], ["`n", "\n"], ["`r", "\r"], [Chr(12), "\f"], [Chr(8), "\b"]]
         obj := StrReplace( obj, v[1], v[2] )

      Return """" obj """"
   }
   sObj := %A_ThisFunc%(obj, true)
   Return JS.eval("JSON.stringify(" . sObj . ",'','" . indent . "')")
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

28

Re: AHK: Помогите переписать JSON_Stringify

Можно без "htmlfile"? Он мне приносил сюрпризы какие-то, о которых сейчас не вспомню. Но не хотелось бы натолкнуться на них еще раз.

29

Re: AHK: Помогите переписать JSON_Stringify

obj := [{test: 1}]
MsgBox, % JSON.Dump(obj,, "`t")

class JSON
{
	/**
	 * Method: Load
	 *     Parses a JSON string into an AHK value
	 * Syntax:
	 *     value := JSON.Load( text [, reviver ] )
	 * Parameter(s):
	 *     value      [retval] - parsed value
	 *     text    [in, ByRef] - JSON formatted string
	 *     reviver   [in, opt] - function object, similar to JavaScript's
	 *                           JSON.parse() 'reviver' parameter
	 */
	class Load extends JSON.Functor
	{
		Call(self, ByRef text, reviver:="")
		{
			this.rev := IsObject(reviver) ? reviver : false
		; Object keys(and array indices) are temporarily stored in arrays so that
		; we can enumerate them in the order they appear in the document/text instead
		; of alphabetically. Skip if no reviver function is specified.
			this.keys := this.rev ? {} : false

			static quot := Chr(34), bashq := "\" . quot
			     , json_value := quot . "{[01234567890-tfn"
			     , json_value_or_array_closing := quot . "{[]01234567890-tfn"
			     , object_key_or_object_closing := quot . "}"

			key := ""
			is_key := false
			root := {}
			stack := [root]
			next := json_value
			pos := 0

			while ((ch := SubStr(text, ++pos, 1)) != "") {
				if InStr(" `t`r`n", ch)
					continue
				if !InStr(next, ch, 1)
					this.ParseError(next, text, pos)

				holder := stack[1]
				is_array := holder.IsArray

				if InStr(",:", ch) {
					next := (is_key := !is_array && ch == ",") ? quot : json_value

				} else if InStr("}]", ch) {
					ObjRemoveAt(stack, 1)
					next := stack[1]==root ? "" : stack[1].IsArray ? ",]" : ",}"

				} else {
					if InStr("{[", ch) {
					; Check if Array() is overridden and if its return value has
					; the 'IsArray' property. If so, Array() will be called normally,
					; otherwise, use a custom base object for arrays
						static json_array := Func("Array").IsBuiltIn || ![].IsArray ? {IsArray: true} : 0
					
					; sacrifice readability for minor(actually negligible) performance gain
						(ch == "{")
							? ( is_key := true
							  , value := {}
							  , next := object_key_or_object_closing )
						; ch == "["
							: ( value := json_array ? new json_array : []
							  , next := json_value_or_array_closing )
						
						ObjInsertAt(stack, 1, value)

						if (this.keys)
							this.keys[value] := []
					
					} else {
						if (ch == quot) {
							i := pos
							while (i := InStr(text, quot,, i+1)) {
								value := StrReplace(SubStr(text, pos+1, i-pos-1), "\\", "\u005c")

								static tail := A_AhkVersion<"2" ? 0 : -1
								if (SubStr(value, tail) != "\")
									break
							}

							if (!i)
								this.ParseError("'", text, pos)

							  value := StrReplace(value,  "\/",  "/")
							, value := StrReplace(value, bashq, quot)
							, value := StrReplace(value,  "\b", "`b")
							, value := StrReplace(value,  "\f", "`f")
							, value := StrReplace(value,  "\n", "`n")
							, value := StrReplace(value,  "\r", "`r")
							, value := StrReplace(value,  "\t", "`t")

							pos := i ; update pos
							
							i := 0
							while (i := InStr(value, "\",, i+1)) {
								if !(SubStr(value, i+1, 1) == "u")
									this.ParseError("\", text, pos - StrLen(SubStr(value, i+1)))

								uffff := Abs("0x" . SubStr(value, i+2, 4))
								if (A_IsUnicode || uffff < 0x100)
									value := SubStr(value, 1, i-1) . Chr(uffff) . SubStr(value, i+6)
							}

							if (is_key) {
								key := value, next := ":"
								continue
							}
						
						} else {
							value := SubStr(text, pos, i := RegExMatch(text, "[\]\},\s]|$",, pos)-pos)

							static number := "number", integer :="integer"
							if value is %number%
							{
								if value is %integer%
									value += 0
							}
							else if (value == "true" || value == "false")
								value := %value% + 0
							else if (value == "null")
								value := ""
							else
							; we can do more here to pinpoint the actual culprit
							; but that's just too much extra work.
								this.ParseError(next, text, pos, i)

							pos += i-1
						}

						next := holder==root ? "" : is_array ? ",]" : ",}"
					} ; If InStr("{[", ch) { ... } else

					is_array? key := ObjPush(holder, value) : holder[key] := value

					if (this.keys && this.keys.HasKey(holder))
						this.keys[holder].Push(key)
				}
			
			} ; while ( ... )

			return this.rev ? this.Walk(root, "") : root[""]
		}

		ParseError(expect, ByRef text, pos, len:=1)
		{
			static quot := Chr(34), qurly := quot . "}"
			
			line := StrSplit(SubStr(text, 1, pos), "`n", "`r").Length()
			col := pos - InStr(text, "`n",, -(StrLen(text)-pos+1))
			msg := Format("{1}`n`nLine:`t{2}`nCol:`t{3}`nChar:`t{4}"
			,     (expect == "")     ? "Extra data"
			    : (expect == "'")    ? "Unterminated string starting at"
			    : (expect == "\")    ? "Invalid \escape"
			    : (expect == ":")    ? "Expecting ':' delimiter"
			    : (expect == quot)   ? "Expecting object key enclosed in double quotes"
			    : (expect == qurly)  ? "Expecting object key enclosed in double quotes or object closing '}'"
			    : (expect == ",}")   ? "Expecting ',' delimiter or object closing '}'"
			    : (expect == ",]")   ? "Expecting ',' delimiter or array closing ']'"
			    : InStr(expect, "]") ? "Expecting JSON value or array closing ']'"
			    :                      "Expecting JSON value(string, number, true, false, null, object or array)"
			, line, col, pos)

			static offset := A_AhkVersion<"2" ? -3 : -4
			throw Exception(msg, offset, SubStr(text, pos, len))
		}

		Walk(holder, key)
		{
			value := holder[key]
			if IsObject(value) {
				for i, k in this.keys[value] {
					; check if ObjHasKey(value, k) ??
					v := this.Walk(value, k)
					if (v != JSON.Undefined)
						value[k] := v
					else
						ObjDelete(value, k)
				}
			}
			
			return this.rev.Call(holder, key, value)
		}
	}

	/**
	 * Method: Dump
	 *     Converts an AHK value into a JSON string
	 * Syntax:
	 *     str := JSON.Dump( value [, replacer, space ] )
	 * Parameter(s):
	 *     str        [retval] - JSON representation of an AHK value
	 *     value          [in] - any value(object, string, number)
	 *     replacer  [in, opt] - function object, similar to JavaScript's
	 *                           JSON.stringify() 'replacer' parameter
	 *     space     [in, opt] - similar to JavaScript's JSON.stringify()
	 *                           'space' parameter
	 */
	class Dump extends JSON.Functor
	{
		Call(self, value, replacer:="", space:="")
		{
			this.rep := IsObject(replacer) ? replacer : ""

			this.gap := ""
			if (space) {
				static integer := "integer"
				if space is %integer%
					Loop, % ((n := Abs(space))>10 ? 10 : n)
						this.gap .= " "
				else
					this.gap := SubStr(space, 1, 10)

				this.indent := "`n"
			}

			return this.Str({"": value}, "")
		}

		Str(holder, key)
		{
			value := holder[key]

			if (this.rep)
				value := this.rep.Call(holder, key, ObjHasKey(holder, key) ? value : JSON.Undefined)

			if IsObject(value) {
			; Check object type, skip serialization for other object types such as
			; ComObject, Func, BoundFunc, FileObject, RegExMatchObject, Property, etc.
				static type := A_AhkVersion<"2" ? "" : Func("Type")
				if (type ? type.Call(value) == "Object" : ObjGetCapacity(value) != "") {
					if (this.gap) {
						stepback := this.indent
						this.indent .= this.gap
					}

					is_array := value.IsArray
				; Array() is not overridden, rollback to old method of
				; identifying array-like objects. Due to the use of a for-loop
				; sparse arrays such as '[1,,3]' are detected as objects({}). 
					if (!is_array) {
						for i in value
							is_array := i == A_Index
						until !is_array
					}

					str := ""
					if (is_array) {
						Loop, % value.Length() {
							if (this.gap)
								str .= this.indent
							
							v := this.Str(value, A_Index)
							str .= (v != "") ? v . "," : "null,"
						}
					} else {
						colon := this.gap ? ": " : ":"
						for k in value {
							v := this.Str(value, k)
							if (v != "") {
								if (this.gap)
									str .= this.indent

								str .= this.Quote(k) . colon . v . ","
							}
						}
					}

					if (str != "") {
						str := RTrim(str, ",")
						if (this.gap)
							str .= stepback
					}

					if (this.gap)
						this.indent := stepback

					return is_array ? "[" . str . "]" : "{" . str . "}"
				}
			
			} else ; is_number ? value : "value"
				return ObjGetCapacity([value], 1)=="" ? value : this.Quote(value)
		}

		Quote(string)
		{
			static quot := Chr(34), bashq := "\" . quot

			if (string != "") {
				  string := StrReplace(string,  "\",  "\\")
				; , string := StrReplace(string,  "/",  "\/") ; optional in ECMAScript
				, string := StrReplace(string, quot, bashq)
				, string := StrReplace(string, "`b",  "\b")
				, string := StrReplace(string, "`f",  "\f")
				, string := StrReplace(string, "`n",  "\n")
				, string := StrReplace(string, "`r",  "\r")
				, string := StrReplace(string, "`t",  "\t")

				static rx_escapable := A_AhkVersion<"2" ? "O)[^\x20-\x7e]" : "[^\x20-\x7e]"
				while RegExMatch(string, rx_escapable, m)
					string := StrReplace(string, m.Value, Format("\u{1:04x}", Ord(m.Value)))
			}

			return quot . string . quot
		}
	}

	/**
	 * Property: Undefined
	 *     Proxy for 'undefined' type
	 * Syntax:
	 *     undefined := JSON.Undefined
	 * Remarks:
	 *     For use with reviver and replacer functions since AutoHotkey does not
	 *     have an 'undefined' type. Returning blank("") or 0 won't work since these
	 *     can't be distnguished from actual JSON values. This leaves us with objects.
	 *     Replacer() - the caller may return a non-serializable AHK objects such as
	 *     ComObject, Func, BoundFunc, FileObject, RegExMatchObject, and Property to
	 *     mimic the behavior of returning 'undefined' in JavaScript but for the sake
	 *     of code readability and convenience, it's better to do 'return JSON.Undefined'.
	 *     Internally, the property returns a ComObject with the variant type of VT_EMPTY.
	 */
	Undefined[]
	{
		get {
			static empty := {}, vt_empty := ComObject(0, &empty, 1)
			return vt_empty
		}
	}

	class Functor
	{
		__Call(method, ByRef arg, args*)
		{
		; When casting to Call(), use a new instance of the "function object"
		; so as to avoid directly storing the properties(used across sub-methods)
		; into the "function object" itself.
			if IsObject(method)
				return (new this).Call(method, arg, args*)
			else if (method == "")
				return (new this).Call(arg, args*)
		}
	}
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

30

Re: AHK: Помогите переписать JSON_Stringify

С данным классом тоже были проблемы.. Вроде значения грузились или сохранялись как-то не правильно.
Давайте попробуем исправить изначальный код?

31

Re: AHK: Помогите переписать JSON_Stringify

msgbox, % JSON.Stringify([{"test": 1}], "`t")

class JSON
{
	Stringify(value, space := 0, _indent := 1)
	{
		_space := space
		if (space ~= "^\d+$")
		{
			_space := strRepeat(A_Space, space)
		}

		str := ""
		array := true
		for k in value
		{
			if (k == A_Index)
			{
				continue
			}

			array := false
			break
		}

		indent := _indent
		for a, b in value
		{
			if (space)
			{
				str .= strRepeat(_space, indent)
			}

			str .= (array ? "" : """" a """: ")

			if (IsObject(b))
			{
				if (space)
				{
					str := RTrim(str, " ") "`n" strRepeat(_space, indent)
				}

				str .= this.Stringify(b, space, indent + 1)
			}
			else
			{
				str .= """" StrReplace(b, """", "\""") """"
			}

			str .= "," (space ? "`n" : " ")
		}
		str := RTrim(str, " ,`n")
		str := RegExReplace(str, "`n([\s]+)?`n", "`n")

		indent--
		out := (array ? "[" : "{") (space ? "`n" : "") str (space ? "`n" : "")
		out .= (space ? strRepeat(_space, indent) : "") (array ? "]" : "}")
		return RegExReplace(out, "^(\h*(\R|$))+|\R\h*(?=\R|$)")
	}
}

strRepeat(str, count)
{
	return StrReplace(Format("{:" count "}", ""), " ", str)
}
Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

32

Re: AHK: Помогите переписать JSON_Stringify

Огромное спасибо. Регулярные выражения сила, правда ничего не понимаю именно в таких. Слишком сложно выглядит.

33

Re: AHK: Помогите переписать JSON_Stringify

Здесь просто пустые строки убираются.

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