Что нового

ObjPath доступ к вложенным scripting.dictionary через точку

inververs

AutoIT Гуру
Сообщения
2,135
Репутация
465
Эта функция позволяет получить доступ к значению или записать значение в объектах scripting.dictionary любого уровня вложенности, используя при этом простой синтаксис.
К примеру, есть объект с такой структурой: (2D массив)
Код:
{
	"a": {
		"name": "vasa",
		"age": "29"
	},
	"b": {
		"name": "sasha",
		"age": "30"
	}
}
В нем имя sasha лежит по следующему пути: b.name, а ее возраст: b.age
Путь, это простое перечисление ключей слева направо.

Код:
$name = ObjPath($object, 'b.name')
$age = ObjPath($object, 'b.age')


Видишь, в пути я использую точку для разделения и мне кажется это гораздо проще чем писать как то так:
$object.item('b').item('name')

Т.к вложенность не ограничена, то давай добавим туда страну.

Добавим город и одну улицу: x.belarus.minsk.street = 'Lenina'
Код:
ObjPath($object, 'x.belarus.minsk.street', 'Lenina')


После этого наш объект станет таким:
Код:
{
	"a": {
		"name": "vasa",
		"age": "29"
	},
	"b": {
		"name": "sasha",
		"age": "30"
	},
	"x": {
		"belarus": {
			"minsk": {
				"street": "Lenina"
			}
		}
	}
}
Хотя раньше ветка x не существовала, она создалась. Очень удобно.

Добавим еще город brest и район в город minsk
Код:
ObjPath($object, 'x.belarus.brest.street', 'Lenina')
ObjPath($object, 'x.belarus.minsk.area', 'Centr')

Теперь объект такой:
Код:
{
	"a": {
		"name": "vasa",
		"age": "29"
	},
	"b": {
		"name": "sasha",
		"age": "30"
	},
	"x": {
		"belarus": {
			"minsk": {
				"street": "Lenina",
				"area": "Centr"
			},
			"brest": {
				"street": "Lenina"
			}
		}
	}
}

Ну, а если точка уже занята, то нет проблем, используй любой другой символ, хоть ->
x->belarus->minsk->street

По пути можно вернуть целый объект. Просто укажи путь до него, скажем всю Беларусь можно вернуть по пути: x.belarus
Код:
$ret = ObjPath($object, 'x.belarus')

Проще простого.

Вот такое вот удобное хранилище получилось. Пользуйтесь на здоровье.
[box title=Функция ObjPath]
Первый параметр - это объект scripting.dictionary
Второй ($path)- это путь
Третий ($value) используется для записи нового значения.
Четвертый ($delimiter) - это разделитель.[/box]
Код:
Func ObjPath($object = Default, $path = Default, $value = Default, $delimiter = '.')
	If $object = Default Then
		$object = ObjCreate('Scripting.Dictionary')
		If $path = Default Then
			Return $object
		EndIf
	EndIf

	If Not IsObj($object) Then
		Return SetError(1, 'Не является объектом', '')
	EndIf

	Local Static $STR_ENTIRESPLIT = 1
	Local $get = $value = Default, $set = Not $get
	Local $item, $current = $object
	Local $split = StringSplit($path, $delimiter, $STR_ENTIRESPLIT)

	For $index = 1 To $split[0]
		$item = $split[$index]

		If $index = $split[0] And $set Then
			;Т.к объект не добавляется через $current.item($item) = $value, то удалим старый и запишем новый
			If IsObj($value) Then
				$current.remove($item)
				$current.add($item, $value)
			Else
				$current.item($item) = $value
			EndIf
			Return SetError(@error, 0, $object)
		EndIf

		If Not IsObj($current) Then
			Return SetError(2, 'Попытка доступа не к объекту', '')
		EndIf

		If Not $current.exists($item) Then
			If $get Then
				Return SetError(3, 'Элемент не найден', '')
			Else
				$current.add($item, ObjCreate('scripting.dictionary'))
			EndIf
		EndIf

		$current = $current.item($item)
	Next

	Return $current
EndFunc   ;==>ObjPath



UPD: Теперь объекты можно переписывать
 
  • Like
Реакции: Norm

DarWiM

Продвинутый
Сообщения
527
Репутация
90
inververs
Замечательно! Осталось придумать применение :smile:
 

joiner

Модератор
Локальный модератор
Сообщения
3,556
Репутация
628
inververs
может я что то не понимаю, но возвращает пустую строку
Код:
$object = ObjCreate('scripting.dictionary')
ConsoleWrite(VarGetType($object)&@LF)
ObjPath($object, 'x.belarus.minsk.street', 'Lenina')
$ret = ObjPath($object, 'belarus')
ConsoleWrite(VarGetType($ret) & @LF & $ret & @LF)
 
Автор
inververs

inververs

AutoIT Гуру
Сообщения
2,135
Репутация
465
joiner [?]
inverversможет я что то не понимаю, но возвращает пустую строку
Поправил. Ошибся в примере. Путь конечно же будет x.belarus.
Код:
$object = ObjCreate('scripting.dictionary')
ConsoleWrite(VarGetType($object)&@LF)
ObjPath($object, 'x.belarus.minsk.street', 'Lenina')
$ret = ObjPath($object, 'x.belarus')
ConsoleWrite(VarGetType($ret) & @LF & $ret & @LF)



Добавлено:
Сообщение автоматически объединено:

DarWiM [?]
Замечательно! Осталось придумать применение
Я делаю проект в котором нужно хранить о машине много данных. Данные представлены в виде json.
Код:
        "car_name": "Audi A 5 Sportback 3,0 TDI DPF S-tronic quattro",
	"car_gebot": 24900,
	"car_brand_name": "Audi",
	"car_cubic": 3000,
	"car_model_name": "A 5",
	"car_model_id": "31",
        "searchHistory": {
		"2014\/10\/13": {
			"hitsCount": 0,
			"searchRequest": "....."
		},
		"2014\/10\/19": {
			"hitsCount": 0,
			"searchRequest": "....."
			}
		}
И с помощью этой функции я могу легко узнать, скажем, модель. Я просто напишу car_model_name, или мне нужно знать есть ли данные по поиску за 13 октября: searchHistory.2014/10/13, а если есть, то узнать количество: searchHistory.2014/10/13.hitsCount
Или я хочу добавить новую дату. Я просто пишу searchHistory.2014/10/24.hitsCount = 1 и searchHistory.2014/10/24.searchRequest = 'blabla'
И все иерархия сама создается. Удобно.
 

joiner

Модератор
Локальный модератор
Сообщения
3,556
Репутация
628
inververs [?]
Поправил. Ошибся в примере. Путь конечно же будет x.belarus.
я так тоже пробовал. результат - пустая строка
работает только так
Код:
$object = ObjCreate('scripting.dictionary')
ObjPath($object, 'x.belarus.minsk.street', 'Lenina')
$ret = ObjPath($object, 'x.belarus.minsk.street')

любой другой вариант возвращает пустую строку
 

ArvenPK

Новичок
Сообщения
14
Репутация
1
joiner сказал(а):
inververs [?]
Код:
$object = ObjCreate('scripting.dictionary')
ObjPath($object, 'x.belarus.minsk.street', 'Lenina')
$ret = ObjPath($object, 'x.belarus.minsk.street')

любой другой вариант возвращает пустую строку
Любой другой вариант возвращает объект, а не строку.
 

joiner

Модератор
Локальный модератор
Сообщения
3,556
Репутация
628
да, точно.
тогда я смог получить значения только так
Код:
$object = ObjCreate('scripting.dictionary')
ObjPath($object, 'x.belarus.minsk.street', 'Lenina')

Local $arv[4] = ['x', '.belarus', '.minsk', '.street'], $value
For $n = 0 To 3
	$value &= $arv[$n]
	$ret = ObjPath($object, $value)
	If IsObj($ret) Then
		$array = $ret.Keys
		For $i = 0 To $ret.Count - 1
			ConsoleWrite($array[$i] & @LF)
		Next
	Else
		ConsoleWrite($ret & @LF)
	EndIf
Next
 
Автор
inververs

inververs

AutoIT Гуру
Сообщения
2,135
Репутация
465
joiner [?]
я так тоже пробовал. результат - пустая строка
Ну правильно, объект не может быть представлен в виде строки.


Добавлено:
Сообщение автоматически объединено:

Значение получите так:
Код:
$ret = ObjPath($object, 'x.belarus.minsk.street')
ConsoleWrite($ret & @CRLF)


А, я понял, вы говорите про значения, а я подумал про значение.
Если возвращаете объект, и нужны все значения, то либо через .keys или .values либо через цикл

Код:
$object = ObjCreate('scripting.dictionary')
ObjPath($object, 'x.belarus.minsk.street', 'Lenina')
ObjPath($object, 'x.belarus.minsk.area', 'Center')
$ret = ObjPath($object, 'x.belarus.minsk')
For $item In $ret
	ConsoleWrite($item & ' = ' & $ret.item($item) & @CRLF)
Next

Но это уже другой вопрос, не по теме.
 

ArvenPK

Новичок
Сообщения
14
Репутация
1
Я бы добавил в шапку предупреждение, что подобный код подходит только для json-подобных ассоциативных массивов, потому как принудительно приводит тип ключа к строке:
Код:
$oDict = ObjCreate('Scripting.Dictionary')
ObjPath($oDict, 1, "one")
ConsoleWrite($oDict.item(1) & @LF) ; ""
ConsoleWrite($oDict.item("1") & @LF) ; "one"


Ну и раз изначальная идея была в упрощении синтаксиса, то почему бы не создавать объект прямо внутри? Например:
Код:
Func ObjPath($object = Default, $path = Default, $value = Default, $delimiter = '.')
	If $object = Default Then
		$object = ObjCreate('Scripting.Dictionary')
		If $path = Default Then
			Return $object
		EndIf
	EndIf

	If Not IsObj($object) Then
		Return SetError(1, 'Не является объектом', '')
	EndIf

	Local Static $STR_ENTIRESPLIT = 1
	Local $get = $value = Default, $set = Not $get
	Local $item, $current = $object
	Local $split = StringSplit($path, $delimiter, $STR_ENTIRESPLIT)

	For $index = 1 To $split[0]
		$item = $split[$index]

		If $index = $split[0] And $set Then
			$current.item($item) = $value
			Return $object
		EndIf

		If Not IsObj($current) Then
			Return SetError(2, 'Попытка доступа не к объекту', '')
		EndIf

		If Not $current.exists($item) Then
			If $get Then
				Return SetError(3, 'Элемент не найден', '')
			Else
				$current.add($item, ObjCreate('scripting.dictionary'))
			EndIf
		EndIf

		$current = $current.item($item)
	Next

	Return $current
EndFunc   ;==>ObjPath
Чтобы можно было делать так:
Код:
$oDict = ObjPath() ; новый объект
$oDict = ObjPath(Default, "1.2.3.4.5", "value") ; новый заполненный объект
 
Автор
inververs

inververs

AutoIT Гуру
Сообщения
2,135
Репутация
465
ArvenPK [?]
потому как принудительно приводит тип ключа к строке
Это из за функции StringSplit. После нее все стало строкой.
Поправить можно так:
Код:
If StringIsDigit($item) Then $item = Number($item)


Ну и раз изначальная идея была в упрощении синтаксиса, то почему бы не создавать объект прямо внутри?
Плюсую, хорошая идея.

Обновил первый пост.
 

ArvenPK

Новичок
Сообщения
14
Репутация
1
inververs сказал(а):
Поправить можно так:
Нельзя, потому что тогда наоборот, вот такие ключи будут принудительно приводиться к числу:
Код:
$oDict = ObjCreate('Scripting.Dictionary')
ObjPath($oDict, "1", "one")
ConsoleWrite($oDict.item(1) & @LF) ; "one"
ConsoleWrite($oDict.item("1") & @LF) ; ""

Это уже совсем неправильно. Текущий вариант хотя-бы соответствует формату json, где как раз ключи могут быть только строкой.
 

SyDr

Сидра
Сообщения
651
Репутация
158
Одна и та же функция для установки и получения значения не очень красивое решение.
Не лучше было бы:
Код:
Obj_Get($Object, Const $sPath, Const $sDelim)
Obj_Set($Object, Const $sPath, Const ByRef $vValue, Const $sDelim)


И в бете же уже есть Maps, а JSON очень хорошо на них ложится (моя версия JSMN), не говоря уже о том, что работают они быстрее, чем Scripting.Dictionary.
 
Автор
inververs

inververs

AutoIT Гуру
Сообщения
2,135
Репутация
465
ArvenPK [?]
Нельзя, потому что тогда наоборот, вот такие ключи будут принудительно приводиться к числу
Удалил. Тогда не вижу вариантов как работать с ключами - числами. Оставил только строки. Тем более у scripting dictionary проблемы с 64-bit ключами
SyDr
Годно. Но то что появится Maps, не повод отказываться от scripting dictionary.
За библиотеку спасибо. И попробуйте написать функцию MapPath.

joiner [?]
пара ошибок выпадает. не хватает двух функций
они в бете. Для нового типа данных - Map.
 

SyDr

Сидра
Сообщения
651
Репутация
158
inververs
Да, у Scripting.Dictionary есть свои преимущества. MapPath написать не получится (нормально), потому что ссылок нет (т.е. если делать как в ObjPath, то будет дофига ненужного копирования в get).
С другой стороны, Map и так можно использовать в виде $Map.X.Belarus.Brest или $Map["X"]["Belarus"]["Brest"]. Не хватает только доступа ($Map) как с массивами.
 

Core2Duo76

Новичок
Сообщения
58
Репутация
1
А расскажите, как созданному объекту передать весь текст JSON? Чтобы потом из него выцеплять значения?
 
Автор
inververs

inververs

AutoIT Гуру
Сообщения
2,135
Репутация
465
Через jsmn_decode(или json_decode), смотри ссылку в подписи, они декодируют JSON в словарь, и уже только после этого можно objpath
 
Верх