Исходник J-bp. Расширенная библиотека для работы с JSON в Moonloader | Удобные конфиги. Работа с imgui/mimgui. cData

1736436937546.png

Заголовок: Расширенная библиотека для работы с JSON в Moonloader.
Зависимость: cjson
Установка: Переместить папку jbp из архива в moonlaoder/lib
Подключение: local jbp = require 'jbp'
Библиотека JBP предоставляет простой и надёжный способ работы с данными в скриптах GTA SA. Особенно полезна при работе с указателями и сложными структурами данных, делая код более чистым и понятным.

Использование:
Lua:
local jbp = require "jbp"

-- Создание объекта
local obj = jbp.create({
    name = "test",
    value = 42,
    main = {
        show = imgui.new.bool(false)
    }
})

-- Сохранение в файл с красивым форматированием
obj:save_to_file("data.json", true, 2)
На данный момент реализовано:
    1. decode(json_str) - преобразует JSON строку в таблицу Lua
    2. encode(value, pretty, indent) - преобразует Lua значение в JSON строку
    3. compress(json_str) - минифицирует JSON строку, удаляя пробелы
    4. sanitize(value) - очищает значение от недопустимых для JSON данных
    5. validate(value, schema) - проверяет JSON данные по заданной схеме
    6. path_query(data, path) - ищет значения по JSON Path выражению
    7. pointer_set(data, path, value) - устанавливает значение по указателю
    8. pointer_get(data, path) - получает значение по JSON Pointer
    9. patch(data, patch) - применяет JSON Patch операции к данным
    10. create(initial_data) - создает новый построитель JSON объектов
    11. :set_pointer(path, ptr) - сохраняет указатель в поле
    12. :get_pointer(path) - получает указатель из поля
    13. :set(path, value) - устанавливает значение по пути
    14. :get(path, default) - получает значение по пути
    15. :merge(other) - объединяет с другим JSON объектом
    16. :clone() - создает копию объекта
    17. :keys() - Возвращает массивы верхнего уровня
    18. :clear - Очищает обьект
    19. :remove(path) - Удаляет значение по указанному пути
    20. :to_json(pretty, indent) - Конвертирует в JSON строку
    21. :validate(data, schema) - Валидирует данные в обьекте по схеме
    22. :filter(keys) - Фильтрует объект, оставляя только указанные ключи
    23. :flatten() - Преобразует вложенный объект в плоский с точечной нотацией
    24. :diff(other) - Сравнивает с другой таблицей или обьектом
    25. :watch(callback) - Отслеживает изменения в объекте
    26. .from_json(json_string) - Создает JsonBuilder из JSON строки
    27. :save_to_file(filename, pretty) - сохраняет в файл
    28. .load_from_file(filename) - загружает из файла
    29. Работа с простыми указателями ffi
    30. Работа с cdata mimgui и userdata imgui
    31. Сохранение в Json происходит с форматированием. Ключи сортируются по степени важности.
    [*]

1. API:
  • Создание объекта
  • Lua:
    -- Создание пустого объекта
    local jbp = require 'jbp'
    local empty = jbp.create()
    
    -- Создание с начальными данными
    local obj = jbp.create({
        name = "John",
        age = 25,
        settings = {
            theme = "dark"
        }
    })
2. Базовые операции:
  • set(path, value) - Установка значения
  • Lua:
    local user = jbp.create()
    
    -- Простая установка
    user:set("name", "John")               -- {name = "John"}
    
    -- Вложенные объекты
    user:set("address.city", "Moscow")     -- {name = "John", address = {city = "Moscow"}}
    
    -- Массивы
    user:set("scores[1]", 100)            -- {name = "John", scores = {100}}
    user:set("items[2]", "Sword")         -- {name = "John", items = {nil, "Sword"}}
    
    -- Цепочка вызовов
    user:set("level", 10)
        :set("health", 100)
        :set("mana", 50)
  • get(path, value) - Получение значения
  • Lua:
    local user = jbp.create({
        name = "John",
        stats = {health = 100},
        inventory = {"Sword", "Shield"}
    })
    
    print(user:get("name"))                -- "John"
    print(user:get("stats.health"))        -- 100
    print(user:get("inventory[1]"))        -- "Sword"
    print(user:get("missing", "N/A"))      -- "N/A" (значение по умолчанию)
3. Трансформация:
  • transform(fn) - Преобразование значений
  • Lua:
    local stats = jbp.create({
        health = 100,
        mana = 50,
        name = "Player",
        equipment = {
            weapon = {damage = 25},
            armor = {defense = 15}
        }
    })
    
    -- Умножаем все числа на 2
    local doubled = stats:transform(function(v)
        return type(v) == "number" and v * 2 or v
    end)
    
    print(doubled:to_json(true))
    -- Результат:
    {
        "health": 200,
        "mana": 100,
        "name": "Player",
        "equipment": {
            "weapon": {
                "damage": 50
            },
            "armor": {
                "defense": 30
            }
        }
    }
  • flatten() - Уплощение структуры
  • Lua:
    local nested = jbp.create({
        user = {
            name = "John",
            address = {
                city = "Moscow",
                street = {
                    name = "Lenin",
                    number = 123
                }
            }
        },
        settings = {
            theme = {
                color = "dark",
                font = {
                    size = 12,
                    family = "Arial"
                }
            }
        }
    })
    
    local flat = nested:flatten()
    print(flat:to_json(true))
    -- Результат:
    {
        "user.name": "John",
        "user.address.city": "Moscow",
        "user.address.street.name": "Lenin",
        "user.address.street.number": 123,
        "settings.theme.color": "dark",
        "settings.theme.font.size": 12,
        "settings.theme.font.family": "Arial"
    }
  • filter(keys) - Фильтрация данных
  • Lua:
    local user = jbp.create({
        username = "john_doe",
        password = "secret123",
        email = "john@example.com",
        profile = {
            age = 25,
            avatar = "photo.jpg",
            private = {
                phone = "1234567890",
                notes = "personal info"
            }
        },
        settings = {
            newsletter = true,
            theme = "dark"
        }
    })
    
    -- Оставляем только публичные данные
    local public = user:filter({"username", "profile", "settings"})
    print(public:to_json(true))
    -- Результат:
    {
        "username": "john_doe",
        "profile": {
            "age": 25,
            "avatar": "photo.jpg"
        },
        "settings": {
            "newsletter": true,
            "theme": "dark"
        }
    }
  • clear() - Очистка всех данных
  • Lua:
    local data = jbp.create({
        name = "John",
        age = 25,
        scores = {100, 200, 300}
    })
    
    print(data:to_json())
    -- Вывод: {"name":"John","age":25,"scores":[100,200,300]}
    
    data:clear()
    print(data:to_json())
    -- Вывод: {}
  • clone() - Создание глубокой копии
  • Lua:
    local original = jbp.create({
        player = {
            stats = {
                health = 100,
                mana = 50
            },
            inventory = {"Sword", "Shield"}
        }
    })
    
    local copy = original:clone()
    copy:set("player.stats.health", 80)
    copy:set("player.inventory[1]", "Axe")
    
    print("Original health:", original:get("player.stats.health"))  -- 100
    print("Copy health:", copy:get("player.stats.health"))         -- 80
    print("Original weapon:", original:get("player.inventory[1]"))  -- "Sword"
    print("Copy weapon:", copy:get("player.inventory[1]"))         -- "Axe"
  • merge(other) - Объединение объектов
  • Lua:
    local base_config = jbp.create({
        graphics = {
            quality = "medium",
            resolution = "1920x1080"
        },
        audio = {
            volume = 80
        }
    })
    
    local user_config = {
        graphics = {
            quality = "high",
            vsync = true
        },
        audio = {
            music = true
        }
    }
    
    base_config:merge(user_config)
    print(base_config:to_json(true))
    -- Результат:
    {
        "graphics": {
            "quality": "high",
            "resolution": "1920x1080",
            "vsync": true
        },
        "audio": {
            "volume": 80,
            "music": true
        }
    }
  • from_json(json_string) - Создание из JSON строки
  • Lua:
    local json_str = [[
    {
        "name": "John",
        "stats": {
            "level": 10,
            "experience": 1500
        },
        "achievements": ["Winner", "Explorer"]
    }
    ]]
    
    local obj, error = jbp.from_json(json_str)
    if obj then
        print("Name:", obj:get("name"))
        print("Level:", obj:get("stats.level"))
        print("First achievement:", obj:get("achievements[1]"))
    else
        print("Error:", error)
    end
    -- Вывод:
    -- Name: John
    -- Level: 10
    -- First achievement: Winner
  • to_json(pretty, indent) - Преобразование в JSON строку
  • Lua:
    local data = jbp.create({
        player = {
            name = "Hero",
            stats = {
                level = 30,
                class = "Warrior"
            },
            inventory = {"Sword", "Shield"}
        }
    })
    
    -- Компактный вывод
    print(data:to_json())
    -- Вывод: {"player":{"name":"Hero","stats":{"level":30,"class":"Warrior"},"inventory":["Sword","Shield"]}}
    
    -- Форматированный вывод
    print(data:to_json(true, 2))
    -- Вывод:
    {
      "player": {
        "name": "Hero",
        "stats": {
          "level": 30,
          "class": "Warrior"
        },
        "inventory": [
          "Sword",
          "Shield"
        ]
      }
    }
  • remove(path) - Удаление по пути
  • Lua:
    local data = jbp.create({
        user = {
            name = "John",
            contacts = {
                email = "john@example.com",
                phone = "1234567890"
            },
            settings = {
                theme = "dark",
                notifications = true
            }
        }
    })
    
    -- Удаление отдельного поля
    data:remove("user.contacts.phone")
    
    -- Удаление вложенного объекта
    data:remove("user.settings")
    
    print(data:to_json(true))
    -- Результат:
    {
        "user": {
            "name": "John",
            "contacts": {
                "email": "john@example.com"
            }
        }
    }
4. Сравнение и отслеживание
  • diff(other) - Поиск различий
  • Lua:
    local config_v1 = jbp.create({
        graphics = {
            quality = "high",
            resolution = "1920x1080",
            vsync = false
        },
        audio = {
            volume = 80,
            music = true
        },
        controls = {
            sensitivity = 1.0
        }
    })
    
    local config_v2 = jbp.create({
        graphics = {
            quality = "ultra",
            resolution = "1920x1080",
            vsync = true
        },
        audio = {
            volume = 90,
            music = false
        }
    })
    
    local diff = config_v1:diff(config_v2)
    print(diff:to_json(true))
    -- Результат:
    {
        "graphics.quality": {
            "old": "high",
            "new": "ultra",
            "status": "changed"
        },
        "graphics.vsync": {
            "old": false,
            "new": true,
            "status": "changed"
        },
        "audio.volume": {
            "old": 80,
            "new": 90,
            "status": "changed"
        },
        "audio.music": {
            "old": true,
            "new": false,
            "status": "changed"
        },
        "controls": {
            "old": {
                "sensitivity": 1.0
            },
            "new": null,
            "status": "removed"
        }
    }
  • watch(callback) - Отслеживание изменений
  • Lua:
    local settings = jbp.create({
        volume = 50,
        brightness = 0.8
    })
    
    settings:watch(function(change)
        print(string.format("Changed %s: %s -> %s at %s",
            change.key,
            tostring(change.old_value),
            tostring(change.new_value),
            os.date("%H:%M:%S", change.timestamp)
        ))
    end)
    
    settings.volume = 75
    -- Вывод: Changed volume: 50 -> 75 at 14:30:45
    
    settings.brightness = 1.0
    -- Вывод: Changed brightness: 0.8 -> 1.0 at 14:30:46
5. Валидация
  • validate(schema) - Валидация по схеме
  • Lua:
    local player = jbp.create({
        name = "John",
        level = 25,
        stats = {
            health = 100,
            mana = 50
        },
        inventory = ["Sword", "Shield"],
        settings = {
            difficulty = "hard"
        }
    })
    
    local schema = {
        type = "object",
        properties = {
            name = {type = "string", required = true},
            level = {type = "number", min = 1, max = 100},
            stats = {
                type = "object",
                properties = {
                    health = {type = "number", min = 0, max = 100},
                    mana = {type = "number", min = 0, max = 100}
                }
            },
            inventory = {type = "array"},
            settings = {
                type = "object",
                properties = {
                    difficulty = {type = "string", enum = {"easy", "normal", "hard"}}
                }
            }
        }
    }
    
    local valid, error = player:validate(schema)
    print(valid and "Valid" or error)
    -- Вывод: Valid
    
    -- Пример с ошибкой
    player:set("level", 150)
    valid, error = player:validate(schema)
    print(error)
    -- Вывод: Value 150 greater than maximum 100
6. Файловые операции
  • save_to_file(filepath, pretty, indent) - Сохранение с форматированием и без.
  • Lua:
    local game_state = jbp.create({
        player = {
            name = "Hero",
            level = 30,
            inventory = {"Sword", "Shield", "Potion"},
            position = {x = 100, y = 200, z = 300}
        },
        world = {
            time = "day",
            weather = "sunny",
            enemies = 10
        }
    })
    
    -- Сохранение с форматированием
    
    ---@param filepath string Путь к файлу
    ---@param pretty boolean|nil Форматировать ли вывод
    ---@param indent number|nil Размер отступа при форматиров
    ---@return boolean success Успешно ли сохранение
    ---@return string|nil error Текст ошибки в случае неудачи
    game_state:save_to_file("save.json", true, 2)
    -- Результат в файле save.json:
    {
      "player": {
        "name": "Hero",
        "level": 30,
        "inventory": [
          "Sword",
          "Shield",
          "Potion"
        ],
        "position": {
          "x": 100,
          "y": 200,
          "z": 300
        }
      },
      "world": {
        "time": "day",
        "weather": "sunny",
        "enemies": 10
      }
    }
  • load_from_file(filepath) - Загрузка json из файла
  • Lua:
    local loaded = jbp.load_from_file("save.json")
    if loaded then
        print("Player name:", loaded:get("player.name"))
        print("World time:", loaded:get("world.time"))
    end
В этом спойлере описаны методы и примеры которые позволяют работать с любыми таблицами и строками json в луа. При этом, нет зависимости от создания новой таблицы через библиотеку, для использования вложенных методов, по типу table:method* table:get:set.

1. Прямая работа с JSON​

Традиционный подход:
Код:
local t = {}
local success = t:decode(json_string)
if not success then
    print("Ошибка")
end
Новый подход с JBP:
Lua:
local data, err = jbp.decode(json_string)
if data then
    print("name:", data.name)
else
    print("error:", err) -- Подробное описание ошибки
end
  • Явное возвращение ошибки
  • Нет необходимости создавать временную таблицу
  • Более информативные сообщения об ошибках

2. JSON Path Query​

JSONPath - это способ находить нужные данные в JSON файле, как будто вы ищете файл в папках на компьютере.
Lua:
-- Структура данных
local data = {
    store = {
        books = {
            {
                title = "Война и мир",
                price = 1000,
                categories = {"классика", "роман"}
            },
            {
                title = "Мастер и Маргарита",
                price = 800,
                categories = {"фэнтези", "классика"}
            }
        }
    }
}

-- Получение всех цен
local prices = jbp.path_query(data, "$.store.books[*].price")
-- Результат: [1000, 800]

-- Получение всех книг в категории "классика"
local classics = jbp.path_query(data, "$.store.books[*]")
  • Декларативный синтаксис
  • Меньше кода
  • Меньше вложенных циклов
  • Защита от nil

3. Валидация по схеме​

Валидирует данные в массиве. К примеру если age больше 150, выдаст ошибку с указанием на позицию в таблице. Этот способ так-же есть для обьекта jbp
Lua:
local schema = {
    type = "object",
    properties = {
        name = {type = "string", required = true},
        age = {type = "number", min = 0, max = 150},
        email = {type = "string", pattern = "^[%w.]+@[%w.]+$"}
    }
}

local user = {
    name = "Иван",
    age = 25,
    email = "ivan@mail.com"
}

local is_valid, err = jbp.validate(user, schema)
  • Декларативное описание схемы
  • Автоматическая проверка всех правил
  • Подробные сообщения об ошибках
  • Вложенная валидация объектов

4. JSON Pointer​

JSON Pointer - это более простой способ указать путь к данным в JSON. Он похож на путь к файлу, только использует "/" для разделения.
Lua:
local data = {
    users = {
        {
            name = "Иван",
            contacts = {
                email = "ivan@mail.com"
            }
        }
    }
}

-- Получение email первого пользователя
local email = jbp.pointer_get(data, "/users/0/contacts/email")
Lua:
{
  "магазин": {
    "книги": [
      {"название": "Гарри Поттер", "цена": 1000},
      {"название": "Властелин колец", "цена": 800}
    ]
  }
}

/магазин/книги/0/название - получить название первой книги
/магазин/книги/1/цена - получить цену второй книги

5. JSON Patch​

JSON Patch - Это как список инструкций "что и где изменить".
Lua:
local user = {
    name = "Иван",
    age = 25
}

local patch = {
    {op = "replace", path = "/age", value = 26},
    {op = "add", path = "/city", value = "Москва"}
}

local updated = jbp.patch(user, patch)
  1. Каждое изменение описывается операцией (op):
    • add - "добавить поле city со значением Москва"
    • replace - "заменить age на 26"
    • remove - "удалить поле old_field"
    • copy - "скопировать значение из name в username"
  2. Путь (path) указывает, где делать изменение:
    • /age - изменить поле age в корне
    • /user/name - изменить name внутри объекта user
    • /items/0 - изменить первый элемент массива items

6. Санитизация данных​

Это процесс очистки данных JSON от вредоносных или нежелательных элементов
Lua:
local dirty_data = {
    inf = math.huge,
    nan = 0/0,
    func = function() end,
    valid = "это останется",
    nested = {
        bad = math.huge,
        good = 42
    }
}

local clean = jbp.sanitize(dirty_data)
  • Автоматическая очистка недопустимых значений
  • Рекурсивная обработка вложенных таблиц
  • Сохранение структуры данных
Почему иногда : перед функцией, а иногда .
:
Используется для вложенных методов для обьекта jbp.
Lua:
local basic = jbp.create()
basic:set("name", "John")
basic:set("age", 25)
basic:set("items", {1, 2, 3})
basic:save_to_file("data.json")
. Используется для любых таблиц и строк.
Lua:
local pretty_json = [[
{
"name": "John",
"age": 30,
"city": "New York"
}
]]
local compressed = jbp.compress(pretty_json)

Создание и загрузка
:create(initial_data)Создает новый JSON объект с опциональными начальными данными
@param initial_data table|nil Начальные данные для JSON объекта
@return table JsonBuilder объект с методами для работы с JSON
.from_json(json_string)Создает объект из JSON строки
@param json_string string JSON строка
@return table|nil builder JsonBuilder объект или nil при ошибке
@return string|nil error Текст ошибки в случае неудачи
.load_from_file(filepath)Загружает JSON объект из файла
@param filepath string Путь к файлу
@return table|nil builder JsonBuilder объект или nil при ошибке
@return string|nil error Текст ошибки в случае неудачи
Получение и установка значений
:set(path, value)Устанавливает значение по указанному пути (поддерживает вложенные пути)
@param path string Путь к значению (например "user.name" или "items[1]")
@param value any Значение для установки
@return table self Текущий объект для цепочки вызовов
:get(path, default)Получает значение по пути, возвращает default если путь не найден
@param path string Путь к значению
@param default any Значение по умолчанию, если путь не найден
@return any value Найденное значение или default
:exists(path)Проверяет существование значения по указанному пути
@param path string Путь для проверки
@return boolean exists Существует ли значение
:remove(path)Удаляет значение по указанному пути
@param path string Путь к удаляемому значению
@return table self Текущий объект для цепочки вызовов

:clear()Очищает все данные объекта
@return table self Текущий объект для цепочки вызовов
:clone()Создает глубокую копию объекта
@return table copy Копия текущего объекта
:merge(other)Объединяет текущий объект с другим
@param other table Таблица для объединения
@return table self Текущий объект для цепочки вызовов
:keys()Возвращает массив ключей верхнего уровня
@return table keys Массив ключей

Трансформации
:transform(fn)Применяет функцию преобразования ко всем значениям
@param fn function Функция для преобразования значений
@return table transformed Преобразованный объект
:filter(keys)Создает новый объект только с указанными ключами
@param keys table Массив ключей для сохранения
@return table filtered Отфильтрованный объект
:flatten()Преобразует вложенный объект в плоскую структуру с точечной нотацией
@return table flattened Уплощенный объект
.compressПроизводит сжатие таблицы
@param json_str string JSON строка для минификации
@return string|nil minified Минифицированная строка или nil при ошибке
@return string|nil error Текст ошибки в случае неудачи
.sanitize(value)Очищает значение от недопустимых для JSON значений
@param value any Значение для очистки
@return any sanitized Очищенное значение
.path(data, path)Применяет операции JSON Patch (RFC 6902)
@param data table Данные для изменения
@param patch table Массив операций patch
@return table|nil result Измененные данные или nil при ошибке
@return string|nil error Текст ошибки в случае неудачи


Валидация и сравнение
:validate(schema)Проверяет объект на соответствие схеме
@param value any Данные для валидации
@param schema table Схема для валидации
@return boolean valid Валидны ли данные
@return string|nil error Сообщение об ошибке если не валидны
:diff(other)Находит различия между текущим и другим объектом
@param other table Объект для сравнения
@return table diff Объект с различиями
:watch(callback)Отслеживает изменения в объекте через callback
@param callback function Функция вызываемая при изменениях

Сериализация
:to_json(pretty, indent)Преобразует объект в JSON строку с опциональным форматированием
@param pretty boolean|nil Форматировать ли вывод
@param indent number|nil Размер отступа при форматировании
@return string json_string JSON представление объекта
:save_to_file(filepath, pretty, indent)Сохраняет объект в файл в формате JSON
@param filepath string Путь к файлу
@param pretty boolean|nil Форматировать ли вывод
@param indent number|nil Размер отступа при форматировании
@return boolean success Успешно ли сохранение
@return string|nil error Текст ошибки в случае неудачи

Кодирование и декодирование
.encode(data, format: bool)Кодирование, стандартная функция. True False для форматирования.
.decode(data)Декодирование, стандартная функция

Навигация по данным
.path_query(data, path)Поиск по выражению
@param data table Данные для поиска
@param path string JSON Path выражение (например: $.store.book[*].author)
@return table|nil result Найденные значения или nil при ошибке
@return string|nil error Текст ошибки в случае неудачи
.pointer_get(data, pointer)Получить конкретные данные
@param data table Данные для поиска
@param pointer string JSON Pointer (например: /store/book/0/author)
@return any|nil value Найденное значение или nil если не найдено
@return string|nil error Текст ошибки в случае неудачи
Внимание, для сериализации cdata откройте спойлер "Работа с cdata".
1. Почему нужен специальный подход:
  • JSON не может хранить cdata типы
  • Нужно сохранять и тип, и значение указателя
  • При загрузке нужно восстанавливать оригинальный тип
2. Как использовать:
  • Lua:
    -- Сохранение
    basic:set_pointer("car", vehicle)  -- vehicle это cdata
    
    -- Загрузка
    local car = loaded:get_pointer("car")  -- получаем с оригинальным типом
3. Ограничения:
  • Указатели работают только в текущей сессии*
  • После перезапуска игры нужно получать новые указатели
  • Нельзя использовать сохраненные указатели между сессиями
4. Примеры использования:
Lua:
local jbp = require 'jbp'
local ffi = require 'ffi'

local samem = require "SAMemory"
samem.require "CAutomobile"

function main()
    if not isSampLoaded() or not isSampfuncsLoaded() then return end
    while not isSampAvailable() do wait(100) end

    local basic = jbp.create()

    -- Сохранение машины
    if isCharInAnyCar(playerPed) then
        local car = storeCarCharIsInNoSave(playerPed)
        if car then
            local CarPointer = getCarPointer(car)
            if CarPointer ~= 0 then
                local vehicle = ffi.cast("CAutomobile*", CarPointer)
                basic:set_pointer("vehicle", vehicle)
                basic:save_to_file("moonloader/config/save.json", true, 2)
                print("Saved")
            end
        end
    end

    -- Загрузка и тест
    wait(100)
    local loaded = jbp.load_from_file("moonloader/config/save.json")
    if loaded then
        local vehicle = loaded:get_pointer("vehicle")
        if vehicle then
            vehicle.wheelOffsetZ[0] = 0.5
            vehicle.wheelOffsetZ[1] = 0.5
            vehicle.wheelOffsetZ[2] = 0.5
            vehicle.wheelOffsetZ[3] = 0.5
            print("Modified wheels")
        end
    end

    while true do
        wait(0)
    end
end
5. Как выглядит сохраненный файл:
  • JSON:
    {
      "vehicle":{
        "type":"ctype<struct CAutomobile *>",
        "pointer":480169768
      }
    }
-- Поддерживаемые типы Imgui
  • ImBool - Булево значение
  • ImInt - Целое число
  • ImFloat - Число с плавающей точкой
  • ImBuffer - Текстовый буфер
  • ImVec2 - 2D вектор (x, y)
  • ImVec4 - 4D вектор (x, y, z, w)
Функции возвращают обьекты imgui, что позволяет вам работать с ними, как с обычным обьектом imgui:
Lua:
local config = jbp.create()

-- Для стандартного ImGui
jbp.pointer_set(config, "Imgui/Window/visible", imgui_default.ImBool(false))

--Сохраняем в конфиг
config:save_to_file("moonloader/config/test.json", true, 2)

-- Загружаем конфиг
local loaded = jbp.load_from_file("moonloader/config/test.json")

--Через .pointer_get:
if jbp.pointer_get(loaded, "Imgui/Window/visible").v == false then
print("Visible Pointer = false")
end

--Через стандартный :get
if loaded:get('Imgui.Window.visible').v == false then
print("Visible get = false")
end
Пример работы с Imgui:
Lua:
local imgui = require 'imgui'
local jbp = require 'jbp'
local data = jbp.create()


-- Сохранение

--Можно использовать как :set, так и jbp.pointer_set(data, "/checkbox", imgui.ImBool(true))

data:set("checkbox", imgui.ImBool(true))
data:set("slider", imgui.ImInt(50))
data:set("input", imgui.ImBuffer(256, "Текст"))
data:set("position", imgui.ImVec2(100, 100))
data:set("color", imgui.ImVec4(1, 0, 0, 1))
data:save_to_file("moonloader/config/test.json", true, 2) -- True включить форматирование, 2 - размер отступа


-- Загрузка
local data = jbp.load_from_file("moonloader/config/test.json")

--Так-же можно использовать jbp.pointer_get(data, '/checkbox')

local checkbox = data:get("checkbox") -- imgui.ImBool
local slider = data:get("slider") -- imgui.ImInt
local input = data:get("input") -- imgui.ImBuffer
local pos = data:get("position") -- imgui.ImVec2
local color = data:get("color") -- imgui.ImVec4
Поддерживаемые типы Mimgui:
  • bool[1] - Булево значение
  • int[1] - Целое число
  • float[1] - Число с плавающей точкой
  • char[] - Текстовый буфер
  • ImVec2 - 2D вектор
  • ImVec4 - 4D вектор
Функции возвращают обьекты mimgui, что позволяет вам работать с ними, как с обычным обьектом mimgui:
Lua:
local jbp = require 'jbp'
local imgui = require 'mimgui'
local config = jbp.create()

local config = jbp.create()

-- pointer_set
jbp.pointer_set(config, "Mimgui/Window/visible", new.bool(true))
jbp.pointer_set(config, "Mimgui/Window/position", imgui.ImVec2(100, 100))
jbp.pointer_set(config, "Mimgui/Window/vectors/vector4D", imgui.ImVec4(100, 100, 100, 100))
jbp.pointer_set(config, "Mimgui/Window/input_text", new.char[256]('hello world'))

--Или стандартное set
config:set("Mimgui.Bools.Visible", new.bool(true))
config:set("Mimgui.Ints.SelectedTab", new.int(0))
config:set("Mimgui.Window.position", imgui.ImVec2(100, 100))
config:set("Mimgui.Window.vectors/vector4D", imgui.ImVec4(100, 100, 100, 100))
config:set("Mimgui.Window.Chars.input_text", new.char[256]('hello world'))

--Сохраняем в файл
config:save_to_file("moonloader/config/test.json", true, 2)

-- Загружаем конфиг
local loaded = jbp.load_from_file("moonloader/config/test.json")

--Дальнейшие манипуляции, как с обычными обьектами mimgui
if jbp.pointer_get(loaded, "Mimgui/Window/visible")[0] == true then
    print("Visible Pointer = true")
end

-- Для получения значения из буфера MImGui
local buffer = jbp.pointer_get(loaded, "Mimgui/Window/input_text")
if buffer then
    local text = ffi.string(buffer) -- Получаем строку из буфера
    print("Buffer text:", text)
end

--Через стандартный :get
if loaded:get('Mimgui.Window.visible')[0] == true then
print("Visible get = true")
end

Через присваивание переменной, а затем её использование:

--Для мимгуи:
local VisibleMimgui = loaded:get("Mimgui.Window.visible")[0];
JSON:
{
  "Imgui":{
    "Bools":{
      "ClosedMenu":{
        "__type":"imgui_bool",
        "value":false
      }
    },
    "Buffers":{
      "InputText":{
        "__type":"imgui_buffer",
        "value":"hello world"
      }
    },
    "Ints":{
      "SelectedTab":{
        "__type":"imgui_int",
        "value":0
      }
    },
    "Window":{
      "input_text":{
        "__type":"imgui_buffer",
        "value":"hello world"
      },
      "position":{
        "__type":"imgui_vec2",
        "x":0,
        "y":0
      },
      "size":{
        "__type":"imgui_vec2",
        "x":800,
        "y":600
      },
      "visible":{
        "__type":"imgui_bool",
        "value":false
      }
    }
  },
  "Mimgui":{
    "Bools":{
      "Visible":{
        "__type":"bool",
        "value":true
      }
    },
    "Ints":{
      "SelectedTab":{
        "__type":"int",
        "value":0
      }
    },
    "Strs":{
      "InputText":{
        "__type":"mimgui_buffer",
        "value":"hello world"
      }
    },
    "Window":{
      "input_text":{
        "__type":"mimgui_buffer",
        "value":"hello world"
      },
      "position":{
        "__type":"vec2",
        "x":100,
        "y":100
      },
      "visible":{
        "__type":"bool",
        "value":true
      },
      "vectors":{
        "vector4D":{
          "__type":"vec4",
          "x":100,
          "y":100,
          "z":100,
          "w":100
        }
      }
    }
  }
}

Пример 1: Работа с API​

Lua:
-- Получение данных из API
local response = [[{"users":[{"id":1,"name":"Иван"},{"id":2,"name":"Мария"}]}]]

-- Декодирование и валидация
local data = jbp.decode(response)
local users = jbp.path_query(data, "$.users[*].name")
print("users:", table.concat(users, ", "))

Пример 2: Сохранение конфигурации​

Lua:
local config = {
    window = {
        width = 800,
        height = 600,
        position = {x = 100, y = 100}
    },
    settings = {
        volume = 0.8,
        fullscreen = true
    }
}

-- Сохранение с форматированием
local success = jbp.encode(config, true, 2)

-- Загрузка отдельных настроек
local volume = jbp.pointer_get(config, "/settings/volume")

Пример 3: Обновление данных​

Lua:
local user_data = {
    name = "Иван",
    settings = {
        notifications = true,
        theme = "dark"
    }
}

-- Применение нескольких изменений
local patches = {
    {op = "replace", path = "/settings/theme", value = "light"},
    {op = "add", path = "/settings/language", value = "ru"}
}

local updated = jbp.patch(user_data, patches)

Пример 4: Поиск похожих значений​

Lua:
local data = {
    ["Иван Петров"] = "программист",
    ["Петр Иванов"] = "дизайнер",
    ["Иван Сидоров"] = "менеджер",
    ["Сидор Иванов"] = "программист"
}

-- Функция поиска похожих значений
function find_similar(query, data, by_key)
    local results = {}
    query = query:lower() -- приводим к нижнему регистру для поиска
 
    for k, v in pairs(data) do
        local search_text = by_key and k or v
        search_text = tostring(search_text):lower()
 
        if search_text:find(query) then
            table.insert(results, {key = k, value = v})
        end
    end
 
    return results
end

-- Пример использования
local query = "иван"
local results = find_similar(query, data, true) -- поиск по ключам
-- Результат:
-- {
--   {key = "Иван Петров", value = "программист"},
--   {key = "Иван Сидоров", value = "менеджер"}
-- }

-- Поиск по значению
local prof_query = "прог"
local prof_results = find_similar(prof_query, data, false)
-- Результат:
-- {
--   {key = "Иван Петров", value = "программист"},
--   {key = "Сидор Иванов", value = "программист"}
-- }

Пример 5: Сжатие​

Lua:
local pretty_json = [[
{
"name": "John",
"age": 30,
"city": "New York"
}
]]

local compressed = jbp.compress(pretty_json)

print("Original size:", #pretty_json)
print("Compressed size:", #compressed)
print("Compressed JSON:", compressed)

Продвинутый поиск с расстоянием Левенштейна​

Lua:
-- Функция расчета расстояния Левенштейна
function levenshtein_distance(str1, str2)
    str1, str2 = str1:lower(), str2:lower()
    local len1, len2 = #str1, #str2
    local matrix = {}
 
    for i = 0, len1 do
        matrix[i] = {[0] = i}
    end
    for j = 0, len2 do
        matrix[0][j] = j
    end
 
    for i = 1, len1 do
        for j = 1, len2 do
            local cost = str1:sub(i,i) == str2:sub(j,j) and 0 or 1
            matrix[i][j] = math.min(
                matrix[i-1][j] + 1,      -- удаление
                matrix[i][j-1] + 1,      -- вставка
                matrix[i-1][j-1] + cost  -- замена
            )
        end
    end
 
    return matrix[len1][len2]
end

-- Функция поиска похожих значений с учетом расстояния Левенштейна
function find_similar_advanced(query, data, max_distance)
    local results = {}
    query = query:lower()
    max_distance = max_distance or 3 -- максимальное допустимое расстояние
 
    for k, v in pairs(data) do
        local key_distance = levenshtein_distance(query, k)
        local value_distance = levenshtein_distance(query, tostring(v))
 
        if key_distance <= max_distance or value_distance <= max_distance then
            table.insert(results, {
                key = k,
                value = v,
                key_relevance = 1 - (key_distance / #query),
                value_relevance = 1 - (value_distance / #query)
            })
        end
    end
 
    -- Сортировка по релевантности
    table.sort(results, function(a, b)
        return math.max(a.key_relevance, a.value_relevance) >
               math.max(b.key_relevance, b.value_relevance)
    end)
 
    return results
end

-- Пример использования продвинутого поиска
local database = {
    ["Иван Петров"] = "программист",
    ["Петр Иванов"] = "дизайнер",
    ["Иван Сидоров"] = "менеджер",
    ["Сидор Иванов"] = "программист",
    ["Иван Иванов"] = "аналитик"
}

-- Поиск с опечаткой
local query = "Иван Петроф" -- опечатка в фамилии
local results = find_similar_advanced(query, database)

-- Вывод результатов с релевантностью
for i, result in ipairs(results) do
    print(string.format(
        "%s (%s) - релевантность: %.2f",
        result.key,
        result.value,
        math.max(result.key_relevance, result.value_relevance)
    ))
end

Поиск с учетом категорий​

Lua:
local categorized_data = {
    programmers = {
        ["Иван Петров"] = {
            position = "Senior Developer",
            skills = {"Lua", "Python", "C++"}
        },
        ["Петр Иванов"] = {
            position = "Junior Developer",
            skills = {"Lua", "JavaScript"}
        }
    },
    designers = {
        ["Анна Сидорова"] = {
            position = "UI Designer",
            skills = {"Figma", "Photoshop"}
        }
    }
}

-- Функция поиска по категориям
function search_in_categories(query, data, category)
    local results = {}
    query = query:lower()
 
    local function search_category(cat_data, cat_name)
        for name, info in pairs(cat_data) do
            if name:lower():find(query) then
                table.insert(results, {
                    name = name,
                    category = cat_name,
                    info = info
                })
            else
                -- Поиск по навыкам
                for _, skill in ipairs(info.skills) do
                    if skill:lower():find(query) then
                        table.insert(results, {
                            name = name,
                            category = cat_name,
                            info = info,
                            matched_skill = skill
                        })
                        break
                    end
                end
            end
        end
    end
 
    if category then
        if data[category] then
            search_category(data[category], category)
        end
    else
        for cat_name, cat_data in pairs(data) do
            search_category(cat_data, cat_name)
        end
    end
 
    return results
end

-- Примеры использования
-- Поиск по всем категориям
local results1 = search_in_categories("lua", categorized_data)
-- Найдет всех, кто знает Lua

-- Поиск только среди программистов
local results2 = search_in_categories("junior", categorized_data, "programmers")
-- Найдет только Junior Developer

-- Вывод результатов
for _, result in ipairs(results1) do
    print(string.format(
        "%s (%s) - %s",
        result.name,
        result.category,
        result.info.position
    ))
    if result.matched_skill then
        print("Найдено по навыку:", result.matched_skill)
    end
end


Рекомендации по использованию:
  1. Выбор метода доступа к данным:
    • Используйте path_query для сложных запросов и массивов
    • Используйте pointer_get для прямого доступа к известным путям
    • Используйте стандартный доступ для простых операций
    • Используйте сжатие для больших JSON
    • Избегайте излишней валидации для доверенных данных
    • Используйте схемы для критичных данных
    • Применяйте санитизацию для пользовательского ввода

1. Подключение библиотеки​

Lua:
local jbp = require 'jbp'

2. Создание и сохранение данных​

Lua:
-- Создаем новый объект
local config = jbp.create()

-- Сохраняем простые значения
config:set("name", "John")
config:set("health", 100)
config:set("weapons", {"Desert Eagle", "M4"})

-- Сохраняем вложенные данные
config:set("stats.strength", 80)
config:set("stats.agility", 75)

-- Сохраняем в файл
config:save_to_file("moonloader/config/my_config.json", true, 2)

3. Загрузка и использование данных​

Lua:
-- Загружаем файл
local data = jbp.load_from_file("moonloader/config/my_config.json")
if data then
    -- Получаем значения
    local name = data:get("name")                -- "John"
    local strength = data:get("stats.strength")  -- 80
    local weapons = data:get("weapons")          -- {"Desert Eagle", "M4"}
end

Разное:
Lua:
-- Создание с начальными данными
local profile = jbp.create({
    name = "John",
    level = 1,
    inventory = {"Pistol"}
})
Lua:
-- Значение по умолчанию
local money = profile:get("money", 0)  -- Вернёт 0 если money не найден
Lua:
-- Проверка существования
if profile:get("weapon.ammo") then
    -- Значение существует
end

1. Установка библиотеки​

Lua:
-- Скачайте файл jbp.lua и поместите его в папку moonloader/lib
-- В начале скрипта подключите библиотеку:
local jbp = require 'jbp'

2. Создание простого конфига​

Lua:
-- Создаем новый конфиг
local config = jbp.create()

-- Добавляем настройки
config:set("name", "Мой скрипт")
config:set("version", 1.0)
config:set("settings.enabled", true)
config:set("settings.hotkey", 0x42) -- клавиша B

-- Сохраняем в файл
config:save_to_file("moonloader/config/myscript.json")

3. Загрузка конфига​

Lua:
-- Создаем конфиг и загружаем из файла
local loaded = jbp.load_from_file("moonloader/config/myscript.json")
if not loaded then
    -- Если файл не существует, создаем стандартные настройки
    config:set("name", "Мой скрипт")
    config:set("version", 1.0)
    config:set("settings.enabled", true)
    config:set("settings.hotkey", 0x42)
    -- Сохраняем стандартные настройки
    config:save_to_file("moonloader/config/myscript.json")
end

4. Различные функции​

Lua:
-- Создание вложенных настроек
config:set("players.max", 50)
config:set("players.list[1].name", "Player1")
config:set("players.list[1].score", 100)
config:set("players.list[2].name", "Player2")
config:set("players.list[2].score", 200)

-- Получение значений
local maxPlayers = config:get("players.max") -- 50
local player1Name = config:get("players.list[1].name") -- "Player1"

-- Работа с массивами
local weapons = {0x1, 0x2, 0x3, 0x4}
config:set("weapons", weapons)

-- Слияние конфигов
local otherConfig = jbp.create({
    newSetting = true,
    extraValue = 123
})
config:merge(otherConfig)

-- Клонирование конфига
local configBackup = config:clone()


Прикрепил файл test_lua.lua с примерами работы с библиотекой.
Прикрепил файл gui_examples.lua с примерами Imgui и Mimgui используя данную библиотеку.

Обновлено: 17.02.2025 9:49
09.01.2025. 01:06: .sanitize, .decode, .encode, .compress, .validate, .path_query, .pointer_get, .path
09.01.2025. 18:36: Работа с cdata mimgui, userdata imgui
17.02.2025 9:49: Обновлена функция path_query. Убраны захардкодженые значения и улучшен поиск. Множественные индексы, wildcard массива items[*]
16.07.2025 - Патч :diff, теперь корректно сравнивает изменения с mimgui imgui значениями
 

Вложения

  • test_lua.lua
    5.5 KB · Просмотры: 11
  • gui_examples.lua
    4.4 KB · Просмотры: 13
  • jbp.rar
    21.9 KB · Просмотры: 3
Последнее редактирование:

UBP

Технический специалист
Автор темы
Проверенный
370
254
Последнее редактирование:

chromiusj

fullstack eblan
Модератор
5,825
4,157
Я только начал разработку, буду пробовать. Создание темы мискликнул и не вписал все что хотел.
Скоро обновлю


Обновил, добавил сохранение cdata,
я вообще имел ввиду как будут храниться данные из инпутов mimgui, как в carbjsonconfig типа,но это тоже прикольно
 
  • Нравится
Реакции: UBP

UBP

Технический специалист
Автор темы
Проверенный
370
254
я вообще имел ввиду как будут храниться данные из инпутов mimgui, как в carbjsonconfig типа,но это тоже прикольно
Я понял. Работаю над обновлением. Сейчас в планах реализовать сериализацию сложных структур ffi без reflection.
Насчёт мимгуи, это точно будет. Скоро
 
  • Нравится
Реакции: unculer и chromiusj

UBP

Технический специалист
Автор темы
Проверенный
370
254
Обновлено:
Функция path_query(data, path):
Убраны захардкодженые значения поиска, добавлена поддержка множественных индексов [0][1], обработка wildcard массива: items[*].
 

UBP

Технический специалист
Автор темы
Проверенный
370
254
Обновлено, добавлено кеширование, обработчик ошибок, собственный json парсер
 

UBP

Технический специалист
Автор темы
Проверенный
370
254
Вернул cjson как зависимость для парсинга некоторых json строк и кодинга/декодинга