for Python
Overview
- Deserialize/serialize JSON.
- Tiny language
- The embedded scripting language
- Dynamically typing
- Lightweight language
- The extension of JSON (JSON's object is valid statements as script.)
Use cases
- In Python Application
- Read/Write JSON's value.
- Change the behavior of Application with downloaded script.
- Change the operation of data with embedded script within data.
- In command line
- Manipulate JSON's value with script.
Example
In Python,
from vivjson.viv import Viv, Json, Method
data = '{"foo": 10, "bar": 30, "baz": 20}'
# JSON's object will be dict.
value, error_message = Viv.run(data)
print(value) # {'foo': 10, 'bar': 30, 'baz': 20}
print(type(value)) # <class 'dict'>
# The specific variable's value
value, error_message = Viv.run(data, 'return(foo)')
print(value) # 10
print(type(value)) # <class 'int'>
# The undefined value is evaluated as null (None).
value, error_message = Viv.run(data, 'return(qux)')
print(value) # None
print(type(value)) # <class 'NoneType'>
# The existence can be checked with "in" operator.
# Note that "." of the right hand side represents this block
# (this local scope).
# "." as the operand is permitted only the right hand side of
# "in" operator.
# Carefully, it can't check about the variable whose prefix is "_".
value, error_message = Viv.run(data, 'return("qux" in .)')
print(value) # False
print(type(value)) # <class 'bool'>
# The calculated value
value, error_message = Viv.run(data, 'return(foo + bar + baz)')
print(value) # 60
# Find maximum value.
# 1. Assignment: pairs = {"foo": 10, "bar": 30, "baz": 20}
# 2. for-loop with iterator: for (pair in pairs) {...}
# - pair[0] is the above key.
# - pair[1] is the above value.
# 3. Update max.
# 4. Return it.
code = 'max=-1, for (pair in pairs) {if (max < pair[1]) {max = pair[1]}}, return(max)'
value, error_message = Viv.run('pairs = ', '+', data, code)
print(value) # 30
# Note that "+" of arguments is concatenation. Of course, the following
# representation can be accepted.
value, error_message = Viv.run('pairs = ' + data, code)
print(value) # 30
# This is realized as below. However, such code may generate the unexpected
# result because "." represents this block that has the variable "max".
code = 'max=-1, for (pair in .) {if (max < pair[1]) {max = pair[1]}}, return(max)'
value, error_message = Viv.run(data, code)
print(value) # 30
# When "_max" is used instead of "max", it is improved.
# Because "." does not treat the variable whose prefix is "_".
code = '_max=-1, for (pair in .) {if (_max < pair[1]) {_max = pair[1]}}, return(_max)'
value, error_message = Viv.run(data, code)
print(value) # 30
# In default, both of script's code and JSON value are accepted.
# Json class is useful if you want to accept as JSON value rather
# than script's code.
data = '{"foo": 3}'
code = 'return(foo)'
value, error_message = Viv.run(Json(data), code)
print(value) # 3
# Using class instance, implicit variable, and member's access
# - When the given value is not JSON object (key-value pairs),
# it is assigned into implicit variable.
# The implicit variable is "_" if there is one value. Otherwise,
# "_[0]", "_[1]", ... are used.
# - A class member is selected like foo.bar and foo['bar'].
# In the following example, "_.2" and "_[2]".
data = '["foo", 10, [{"bar": null, "baz": "test"}, false]]'
instance, error_message = Viv.make_instance(data)
value, error_message = Viv.run(instance, "return(_[2][0]['bar'])")
print(value) # None (null is represented as None in Python.)
value, error_message = Viv.run(instance, "return(_.2.-2.baz)")
print(value) # test
# Calling class method
# - Method object has 2 elements.
# - 1st element is method name as str.
# - 2nd element is its arguments as list/tuple.
# In the following example, Method('add', [10, 20]).
# It is equivalent to "return(add(10, 20))".
code = 'function add(a, b) {' \
' return(a + b)' \
'}'
instance, error_message = Viv.make_instance(code)
value, error_message = Viv.run(instance, Method('add', [10, 20]))
print(value) # 30
value, error_message = Viv.run(instance, 'return(add(10, 20))')
print(value) # 30
In command-line,
# The specific variable's value
vivjson '{"foo": 10, "bar": 30, "baz": 20}' 'return(foo)' # 10
# or
python3 -m vivjson '{"foo": 10, "bar": 30, "baz": 20}' 'return(foo)' # 10
# Using PIPE (-i option)
echo '{"foo": 10, "bar": 30, "baz": 20}' | vivjson -i 'return(foo)' # 10
# The calculated value
echo '{"foo": 10, "bar": 30, "baz": 20}' | \
vivjson -i 'return(foo + bar + baz)' #60
# Find maximum value.
echo '{"foo": 10, "bar": 30, "baz": 20}' | vivjson -i=pairs \
'max=-1, for (pair in pairs) {if (max < pair[1]) {max = pair[1]}}, return(max)' # 30
# Find maximum value without PIPE.
vivjson "pairs=" + '{"foo": 10, "bar": 30, "baz": 20}' \
'max=-1, for (pair in pairs) {if (max < pair[1]) {max = pair[1]}}, return(max)' # 30
# Note that "+" of arguments is concatenation. Of course, the following
# representation can be accepted.
vivjson 'pairs={"foo": 10, "bar": 30, "baz": 20}' \
'max=-1, for (pair in pairs) {if (max < pair[1]) {max = pair[1]}}, return(max)' # 30
# Getting member's value
# "return(foo.bar)" and "return(foo['bar'])" are equivalent.
vivjson '{"foo": [1, {"bar": true}, "test"]}' 'return(foo[0])' # 1
vivjson '{"foo": [1, {"bar": true}, "test"]}' 'return(foo.1.bar)' # true
vivjson '{"foo": [1, {"bar": true}, "test"]}' 'return(foo.-1)' # test
# Using implicit variable
# When the given value is not JSON object (key-value pairs),
# it is assigned into implicit variable.
# The implicit variable is "_" if there is one value. Otherwise,
# "_[0]", "_[1]", ... are used.
vivjson 1.5 'return(_)' # 1.5
vivjson 1.5 2 'return(_[0] + _[1])' # 3.5
echo '[{"name": "dog", "number": 2}, {"name": "cat", "number": 3}]' | \
vivjson -i 'result = {}' \
'for (data in _) {result[data.name] = data.number}' \
'return(result)' # {"dog": 2, "cat": 3}
# Help
vivjson
Installation
Python version: >= 3.9
python3 -m pip install vivjson
License
Licensed under the Apache License, Version 2.0.
API
| Pattern | Consumed memory | Next running speed |
|---|---|---|
| Direct running | Low | Slow |
| Parsing and Running | Middle | Middle |
| Making class instance and Running | High | Fast |
When class instance is made, class method can be called and member's variable can be updated.
Direct running
It is suitable if running times is only one.
+--------------------+
| |
| Viv |
| |
| +--------------+ |
Python's | | | | Python's
value ------>| run |------> value
| | | | or
JSON's | | | | JSON's
value ------>| | | value
| | | |
Script | | | |
code ------>| | |
| +--------------+ |
| |
+--------------------+
For example,
value, error_message = Viv.run('{"foo": 3, "bar": 2}', "return(foo + bar)")
print(value) # 5
Parsing and Running
It is suitable that same running is repeated.
Because parsing is done only one time.
+--------------------+ +--------------------+
| | | |
| Viv | | Viv |
| | | |
| +--------------+ | Parsed | +--------------+ |
Python's | | | | value/code | | | | Python's
value ------>| parse, |--------------------->| run |------> value
| | parse_file, | | | | | | or
JSON's | | parse_text | | Additional | | | | JSON's
value ------>| | | Python/JSON's | | | | value
| | | | value -------->| | |
Script | | | | | | | |
code ------>| | | Additional ----->| | |
| +--------------+ | Script code | +--------------+ |
| | | |
+--------------------+ +--------------------+
For example,
parsed, error_message = Viv.parse("return(foo + bar)")
value, error_message = Viv.run('{"foo":3, "bar": 2}', parsed)
print(value) # 5
Making class instance and Running
It is suitable that same running is repeated.
Because parsing and initialization are done only one time.
+---------------------+ +--------------------+
| | | |
| Viv | | Viv |
| | | |
| +---------------+ | | +--------------+ |
Python's | | | | Instance | | | | Python's
value ------>| make_instance |--------------------->| run |------> value
| | | | | | | | or
JSON's | | | | Additional | | | | JSON's
value ------>| | | Python/JSON's | | | | value
| | | | value -------->| | |
Script | | | | | | | |
code ------>| | | Additional ----->| | |
| +---------------+ | Script code | | | |
| | | | | |
+---------------------+ Calling -------->| | |
Method | +--------------+ |
| |
+--------------------+
For example,
code = "function add(a, b) {" \
" return(a + b)" \
"}" \
"c = [20, false]"
instance, error_message = Viv.make_instance(code)
value, error_message = Viv.run(instance, '{"foo":3, "bar": 2}', 'return(add(foo, bar))')
print(value) # 5
value, error_message = Viv.run(instance, Method("add", [3, 2]))
print(value) # 5
value, error_message = Viv.run(instance, "return(c[0])")
print(value) # 20
Viv class
The following methods are available.
- Running/Deserialization function
run: Run VivJson's code or deserialize JSON objects.parse: Parse VivJson's code and JSON object.parse_file: Parse a file that contains VivJson's code or JSON object.parse_text: Parse a text that is VivJson's code or JSON object.make_instance: Makes a class instance.
- String conversion
make_string: Convert into String. Serialize into JSON string.
The following arguments can be given into all methods except make_string.
Note that make_string's argument is Any value.
| Argument type | Python object type | Example |
|---|---|---|
| A VivJson's code | str |
'foo = bar / 2', 'result = test(100)' |
| A JSON value | str |
'{"foo": 10, "bar": true}', '[1, 2]', 'dog', 'null' |
| A JSON value | Json |
Json('{"foo": 10, "bar": true}'), Json('[1, 2]'), Json('dog'), Json('null') |
| A file path | str |
'data/events.json', 'calc.viv' |
| Some VivJson's codes, JSON values, file paths, variables, Parsed objects | list or tuple |
[Json('{"foo": 10, "bar": 1.5}'), 'baz = foo + bar', 'return(baz)'] |
| Some variables (name/value pairs) | dict |
{"foo": "alpha", "bar": true} |
| Some configurations | Config |
Config(infinity="Infinity", nan="NaN") |
| Some parsed statements | Parsed |
Viv.parse('return(a+b)') |
| A class instance | Instance |
Viv.make_instance('{"a": 3, "b": 2}') |
| A calling class-method | Method |
Method('add', [100, -10])The 1st element is the method name as str. The following list is its arguments. |
The calling class-method needs class instance in arguments.
code = 'function add(a, b) {' \
' return(a + b)' \
'}'
instance, error_message = Viv.make_instance(code)
value, error_message = Viv.run(instance, Method('add', [100, -10]))
print(value) # 90
Multiple arguments can be given into all methods except make_string.
Furthermore, an array of arguments can be given too.
For example, value, error_message = Viv.run('{a:3,b:2}', 'return(a+b)') is equivalent to
value, error_message = Viv.run(['{a:3,b:2}', 'return(a+b)']).
There are two value as the returned value except make_string.
Note that make_string's returned value is str.
| Method | First value if succeeded | First value if failed | Second value if succeeded | Second value if failed |
|---|---|---|---|---|
run |
bool, int, float, str, list, dict, None |
None |
"" (empty) |
Error message |
parse |
Parsed |
None |
"" (empty) |
Error message |
parse_file |
Parsed |
None |
"" (empty) |
Error message |
parse_text |
Parsed |
None |
"" (empty) |
Error message |
make_instance |
Instance |
None |
"" (empty) |
Error message |
dict's key is str. Its value is bool, int, float, str, list, dict, or None.list's element is also bool, int, float, str, list, dict, or None.
Config class
The following configurations are available.
| Name | Object type | Default value | Description |
|---|---|---|---|
| enable_stderr | bool |
False |
When True is given, error message is outputted into stderr. |
| enable_tag_detail | bool |
False |
When True is given, error message's tag contains either of "Lexer", "Parser", or "Evaluator". |
| enable_only_json | bool |
False |
When True is given, the given data is parsed as JSON. In other words, script is disabled. |
| infinity | str or None |
None |
When string is given, Infinity is allowed in JSON. Then the given string is used to input/output Infinity from/to JSON. (Note that it is not surrounded with quotation mark.) When None is given and Infinity is happen, error is occurred. |
| nan | str or None |
None |
When string is given, NaN (Not a Number) is allowed in JSON. Then the given string is used to input/output NaN from/to JSON. (Note that it is not surrounded with quotation mark.) When None is given and NaN is happen, error is occurred. |
| max_array_size | int |
1000 |
Maximum array/block size. |
| max_depth | int |
200 |
Maximum recursive called times of evaluate method. |
| max_loop_times | int |
1000 |
Maximum loop times of "for", "while", and so on. |
Each configuration is set/gotten with the following method.
On the other hand, each configuration can be given as an argument of constructor.
| Name | get method | set method |
|---|---|---|
| enable_stderr | get_enable_stderr |
enable_stderr |
| enable_tag_detail | get_enable_tag_detail |
enable_tag_detail |
| enable_only_json | get_enable_only_json |
enable_only_json |
| infinity | get_infinity |
set_infinity |
| nan | get_nan |
set_nan |
| max_array_size | get_max_array_size |
set_max_array_size |
| max_depth | get_max_depth |
set_max_depth |
| max_loop_times | get_max_loop_times |
set_max_loop_times |
For example,
config = Config(max_array_size=10)
config.set_infinity("Infinity")
config.set_nan("NaN")
value, error_message = Viv.run(Json(text), code, config)
By the way, the following JSON object is invalid because JSON's number can't treat Infinity and NaN. (See RFC 8259 Section 6)
However, VivJson can treat it using Config#set_infinity and Config#set_nan.
{
"normal": 1.5,
"inf": Infinity,
"negative_inf": -Infinity,
"nan": NaN,
"str": "1.5"
}