Serialize rust_decimal without a custom scalar type in async-graphql

This is an example of what I would like to achieve without resorting to custom Scalar:

/*
[dependencies]
async-graphql = { version = "7.0.11", default-features = false, features = [
    "decimal",
] }
rust_decimal = { version = "1.36.0", features = [
    "serde-with-arbitrary-precision",
] }
serde = { version = "1.0.215", default-features = false }
serde_json = { version = "1.0.133", default-features = false }
tokio = { version = "1.42.0", features = ["full"] }
*/

use async_graphql::{Context, Object, Scalar, ScalarType, Schema, SimpleObject, Value};
use rust_decimal::Decimal;
use std::str::FromStr;

#[derive(serde::Serialize, serde::Deserialize)]
struct MyDecimal(Decimal);

#[Scalar]
impl ScalarType for MyDecimal {
    fn parse(value: Value) -> async_graphql::InputValueResult<Self> {
        if let Value::String(s) = value {
            Ok(MyDecimal(
                Decimal::from_str(&s).map_err(|_| "Invalid decimal value")?,
            ))
        } else {
            Err("Invalid type for Decimal".into())
        }
    }

    fn to_value(&self) -> Value {
        if let Ok(n) = serde_json::Number::from_str(&self.0.to_string()) {
            Value::Number(n)
        } else {
            Value::String(self.0.to_string())
        }
    }
}

#[derive(SimpleObject, serde::Serialize, serde::Deserialize)]
pub struct CustomTax {
    percentage: MyDecimal,
}

#[derive(SimpleObject, serde::Serialize, serde::Deserialize)]
pub struct Tax {
    #[serde(with = "rust_decimal::serde::arbitrary_precision")]
    percentage: Decimal,
}

struct QueryRoot;

#[Object]
impl QueryRoot {
    async fn get_tax(&self, _ctx: &Context<'_>) -> Tax {
        Tax {
            percentage: Decimal::from_str("123.400").unwrap(),
        }
    }

    async fn get_custom_tax(&self, _ctx: &Context<'_>) -> CustomTax {
        CustomTax {
            percentage: MyDecimal(Decimal::from_str("123.400").unwrap()),
        }
    }
}

#[tokio::main]
async fn main() {
    // let value = Tax {
    //     name: "Pay".to_string(),
    //     percentage: Decimal::from_str("123.400").unwrap(),
    // };

    // assert_eq!(
    //     &serde_json::to_string(&value).unwrap(),
    //     r#"{"name":"Pay","percentage":123.400}"#
    // );

    // --------

    let schema = Schema::new(
        QueryRoot,
        async_graphql::EmptyMutation,
        async_graphql::EmptySubscription,
    );

    let query = "{ getTax { percentage } getCustomTax { percentage } }";

    let response = schema.execute(query).await;

    println!("{}", serde_json::to_string_pretty(&response).unwrap());

    assert_eq!(
        r#"{getTax: {percentage: 123.400}, getCustomTax: {percentage: 123.400}}"#,
        response.data.to_string(),
    );

    // I'm getting instead: {getTax: {percentage: \"123.400\"}, getCustomTax: {percentage: 123.400}}
}

Why the #[serde(with = "rust_decimal::serde::arbitrary_precision")] is not working?

I don't exactly see the connection between the serde_json::Value stored in async_graphql::Response::data and your Tax::percentage field. You never actually deserialise the value stored in in getTax.percentage into a Decimal.

I'm looking for a way to serialize the number as number and not as string and back.

You might be able to achieve this by enabling the rust_decimal/serde-arbitrary-precision feature instead of using #[serde(with = "rust_decimal::serde::arbitrary_precision")], not sure. All I know is if you enable serde_json/arbitrary-precision, serde_json will serialise any serde_json::Number as a JSON string instead of a JSON number:

rust_decimal::serde::arbitrary_precision::serialize simply converts the Decimal to a serde_json::Number and serialises that.