| Crates.io | moosicbox_json_utils |
| lib.rs | moosicbox_json_utils |
| version | 0.1.4 |
| created_at | 2024-10-04 00:12:07.977953+00 |
| updated_at | 2025-07-21 19:11:16.779248+00 |
| description | MoosicBox json utilities package |
| homepage | |
| repository | https://github.com/MoosicBox/MoosicBox |
| max_upload_size | |
| id | 1395846 |
| size | 130,765 |
Simple JSON utility library for the MoosicBox ecosystem, providing type-safe JSON value conversion traits and basic parsing utilities for database and search engine integration.
Add this to your Cargo.toml:
[dependencies]
moosicbox_json_utils = "0.1.1"
use moosicbox_json_utils::{ToValueType, ParseError};
use serde_json::Value;
fn main() -> Result<(), ParseError> {
// Convert JSON value to specific type
let json_value = serde_json::json!("42");
let number: i32 = json_value.to_value_type()?;
assert_eq!(number, 42);
// Convert JSON string to boolean
let json_bool = serde_json::json!("true");
let boolean: bool = json_bool.to_value_type()?;
assert_eq!(boolean, true);
// Convert JSON array to vector
let json_array = serde_json::json!([1, 2, 3, 4, 5]);
let numbers: Vec<i32> = json_array.to_value_type()?;
assert_eq!(numbers, vec![1, 2, 3, 4, 5]);
Ok(())
}
use moosicbox_json_utils::serde_json::{ToNestedValue, get_nested_value};
let metadata = serde_json::json!({
"track": {
"title": "Bohemian Rhapsody",
"artist": "Queen",
"details": {
"duration": 355,
"year": 1975
}
}
});
// Access nested values
let title: String = metadata.to_nested_value(&["track", "title"])?;
let duration: u32 = metadata.to_nested_value(&["track", "details", "duration"])?;
println!("Title: {}, Duration: {}s", title, duration);
use moosicbox_json_utils::ToValueType;
let data = serde_json::json!({
"name": "John",
"age": null
});
// Handle optional values gracefully
let name: String = data.to_value("name")?;
let age: Option<u32> = data.to_value("age")?;
println!("Name: {}, Age: {:?}", name, age); // Age will be None
pub trait ToValueType<T> {
fn to_value_type(self) -> Result<T, ParseError>;
fn missing_value(&self, error: ParseError) -> Result<T, ParseError> {
Err(error)
}
}
pub trait MissingValue<Type> {
fn missing_value(&self, error: ParseError) -> Result<Type, ParseError> {
Err(error)
}
}
pub trait JsonValidator {
fn validate(&self, value: &serde_json::Value) -> Result<(), ParseError>;
}
pub trait JsonSanitizer {
fn sanitize(&self, value: serde_json::Value) -> serde_json::Value;
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum ParseError {
#[error("Failed to parse property: {0:?}")]
Parse(String),
#[error("Failed to convert to type: {0:?}")]
ConvertType(String),
#[error("Missing required value: {0:?}")]
MissingValue(String),
}
#[cfg(feature = "database")]
pub mod database {
use switchy_database::{DatabaseValue, DatabaseError};
pub trait DatabaseJsonExt {
fn from_json(value: &serde_json::Value) -> Result<DatabaseValue, DatabaseError>;
fn to_json(&self) -> Result<serde_json::Value, DatabaseError>;
}
impl DatabaseJsonExt for DatabaseValue {
fn from_json(value: &serde_json::Value) -> Result<DatabaseValue, DatabaseError>;
fn to_json(&self) -> Result<serde_json::Value, DatabaseError>;
}
}
#[cfg(feature = "tantivy")]
pub mod tantivy {
use tantivy::{Document, schema::Field};
pub trait TantivyJsonExt {
fn add_json_object(&mut self, field: Field, value: serde_json::Value);
fn get_json_object(&self, field: Field) -> Option<&serde_json::Value>;
}
impl TantivyJsonExt for Document {
fn add_json_object(&mut self, field: Field, value: serde_json::Value);
fn get_json_object(&self, field: Field) -> Option<&serde_json::Value>;
}
}
#[cfg(feature = "rusqlite")]
pub mod rusqlite {
use rusqlite::{types::Value, Result};
pub trait SqliteJsonExt {
fn from_json_value(value: &serde_json::Value) -> Value;
fn to_json_value(&self) -> Result<serde_json::Value>;
}
impl SqliteJsonExt for Value {
fn from_json_value(value: &serde_json::Value) -> Value;
fn to_json_value(&self) -> Result<serde_json::Value>;
}
}
database: Enable database integration utilitiesrusqlite: Enable SQLite-specific JSON functionstantivy: Enable Tantivy search engine integrationserde_json: Enable enhanced serde_json functionalityJSON_UTILS_VALIDATION_STRICT: Enable strict JSON validation (default: false)JSON_UTILS_SANITIZE_INPUT: Automatically sanitize JSON input (default: true)JSON_UTILS_MAX_DEPTH: Maximum JSON nesting depth (default: 64)use moosicbox_json_utils::{ToValueType, database::DatabaseJsonExt};
use switchy_database::Database;
async fn process_music_library() -> Result<(), Box<dyn std::error::Error>> {
let db = get_database_connection().await?;
// Process album metadata
let album_json = serde_json::json!({
"title": "The Dark Side of the Moon",
"artist": "Pink Floyd",
"release_year": 1973,
"tracks": [
{
"title": "Speak to Me",
"duration": 90,
"track_number": 1
},
{
"title": "Breathe (In the Air)",
"duration": 163,
"track_number": 2
}
],
"genres": ["progressive rock", "psychedelic rock"],
"total_duration": 2532
});
// Store in database
let db_value = DatabaseValue::from_json(&album_json)?;
db.execute(
"INSERT INTO albums (metadata) VALUES (?)",
&[db_value],
).await?;
// Query and process results
let rows = db.query(
"SELECT metadata FROM albums WHERE json_extract(metadata, '$.release_year') > 1970",
&[],
).await?;
for row in rows {
let metadata_value = &row[0];
let album_metadata: serde_json::Value = metadata_value.to_json()?;
let title: String = album_metadata["title"].to_value_type()?;
let artist: String = album_metadata["artist"].to_value_type()?;
let tracks: Vec<serde_json::Value> = album_metadata["tracks"].to_value_type()?;
println!("Album: {} by {}", title, artist);
println!("Tracks: {}", tracks.len());
}
Ok(())
}
use moosicbox_json_utils::tantivy::TantivyJsonExt;
use tantivy::{Index, IndexWriter};
fn build_music_search_index() -> Result<(), Box<dyn std::error::Error>> {
// Create schema
let mut schema_builder = tantivy::schema::Schema::builder();
let metadata_field = schema_builder.add_json_field("metadata", tantivy::schema::STORED);
let schema = schema_builder.build();
let index = Index::create_in_ram(schema.clone());
let mut index_writer = index.writer(50_000_000)?;
// Index music metadata
let music_items = load_music_metadata()?;
for item in music_items {
let mut doc = tantivy::Document::new();
doc.add_json_object(metadata_field, item);
index_writer.add_document(doc)?;
}
index_writer.commit()?;
Ok(())
}
fn load_music_metadata() -> Result<Vec<serde_json::Value>, Box<dyn std::error::Error>> {
// Load music metadata from various sources
Ok(vec![
serde_json::json!({
"type": "track",
"title": "Bohemian Rhapsody",
"artist": "Queen"
}),
serde_json::json!({
"type": "album",
"title": "Abbey Road",
"artist": "The Beatles"
}),
])
}
use moosicbox_json_utils::ParseError;
match json_value.to_value_type::<String>() {
Ok(string_value) => {
println!("Parsed string: {}", string_value);
}
Err(ParseError::Parse(msg)) => {
eprintln!("Parse error: {}", msg);
}
Err(ParseError::ConvertType(msg)) => {
eprintln!("Type conversion error: {}", msg);
}
Err(ParseError::MissingValue(field)) => {
eprintln!("Missing required field: {}", field);
}
}
# Run all tests
cargo test
# Test with database features
cargo test --features database
# Test with Tantivy features
cargo test --features tantivy
# Test SQLite integration
cargo test --features rusqlite
switchy_database - Database abstraction layermoosicbox_search - Search engine functionalitymoosicbox_music_models - Music data models