
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"
}