Skip to content

Commit de5c436

Browse files
authored
fix: Fix serde_as and ordering of the fields in AsNep297Event (#1405)
1 parent 4289b60 commit de5c436

File tree

5 files changed

+88
-29
lines changed

5 files changed

+88
-29
lines changed

near-sdk-macros/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ quote = { version = "1.0", default-features = false }
2323
Inflector = { version = "0.11.4", default-features = false, features = [] }
2424
darling = { version = "0.20.3", default-features = false }
2525
serde = { version = "1", default-features = false, features = ["serde_derive"] }
26-
serde_json = { version = "1", features = ["preserve_order"] }
26+
serde_json = { version = "1" }
2727

2828
[dev-dependencies]
2929
insta = { version = "1.31.0", features = ["yaml"] }

near-sdk-macros/src/lib.rs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,22 @@ pub fn near(attr: TokenStream, item: TokenStream) -> TokenStream {
161161
}
162162
}
163163

164+
// Add serde_as BEFORE schema_derive so that serde_with can process
165+
// the schemars = true parameter and inject schema attributes before
166+
// JsonSchema derive macro runs
167+
if has_json {
168+
let enable_schemars = cfg!(feature = "abi").then_some(quote! {schemars = true,});
169+
expanded = quote! {
170+
#expanded
171+
#[#near_sdk_crate::serde_with::serde_as(
172+
crate = #string_serde_with_crate,
173+
#enable_schemars
174+
)]
175+
#[derive(#near_sdk_crate::serde::Serialize, #near_sdk_crate::serde::Deserialize)]
176+
#[serde(crate = #string_serde_crate)]
177+
};
178+
}
179+
164180
#[cfg(feature = "abi")]
165181
{
166182
let schema_derive: proc_macro2::TokenStream =
@@ -179,19 +195,6 @@ pub fn near(attr: TokenStream, item: TokenStream) -> TokenStream {
179195
};
180196
}
181197

182-
if has_json {
183-
let enable_schemars = cfg!(feature = "abi").then_some(quote! {schemars = true,});
184-
expanded = quote! {
185-
#expanded
186-
#[#near_sdk_crate::serde_with::serde_as(
187-
crate = #string_serde_with_crate,
188-
#enable_schemars
189-
)]
190-
#[derive(#near_sdk_crate::serde::Serialize, #near_sdk_crate::serde::Deserialize)]
191-
#[serde(crate = #string_serde_crate)]
192-
};
193-
}
194-
195198
if let Ok(input) = syn::parse::<ItemStruct>(item.clone()) {
196199
expanded = quote! {
197200
#expanded

near-sdk/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ required-features = ["abi", "unstable"]
2020
[dependencies]
2121
# Provide near_bidgen macros.
2222
serde = { version = "1", features = ["derive"] }
23-
serde_json = "1"
23+
serde_json = { version = "1", features = ["preserve_order"] }
2424
serde_with = { version = "3", features = ["base64", "hex", "json"] }
2525
near-sdk-macros = { path = "../near-sdk-macros", version = "~5.17.2" }
2626
near-sys = { path = "../near-sys", version = "0.2.5" }

near-sdk/src/lib.rs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -353,27 +353,30 @@ compile_error!(
353353
/// You can also use [`#[serde_as(as = "...")]` attributes](https://docs.rs/serde_with/latest/serde_with/attr.serde_as.html)
354354
/// as if `#[serde_as]` is added to the type (which is what actually happens under the hood of `#[near(serializers = [json])]` implementation).
355355
///
356+
/// Note: When using the `abi` feature with base64/hex encoding, prefer the SDK's [`json_types`](crate::json_types)
357+
/// like [`Base64VecU8`](crate::json_types::Base64VecU8), which have full JSON Schema support.
358+
///
359+
/// For hex encoding with ABI support, you can use `#[serde_as(as = "serde_with::hex::Hex")]` by enabling
360+
/// the `serde_with/schemars_1` feature in your `Cargo.toml` (this requires upgrading to schemars 1.x).
361+
///
356362
/// ```
357-
/// # use std::{collections::BTreeMap};
363+
/// # use std::collections::BTreeMap;
358364
/// use near_sdk::{
359365
/// near,
360366
/// serde_json::json,
361-
/// serde_with::{base64::Base64, hex::Hex, json::JsonString, DisplayFromStr},
367+
/// serde_with::{json::JsonString, DisplayFromStr},
368+
/// json_types::Base64VecU8,
362369
/// };
363370
///
364371
/// #[near(serializers = [json])]
365372
/// pub struct MyStruct {
366373
/// #[serde_as(as = "DisplayFromStr")]
367374
/// pub amount: u128,
368375
///
369-
/// #[serde_as(as = "Hex")]
370-
/// pub hex_bytes: Vec<u8>,
371-
///
372-
/// #[serde_as(as = "Base64")]
373-
/// pub base64_bytes: Vec<u8>,
376+
/// pub base64_bytes: Base64VecU8,
374377
///
375-
/// #[serde_as(as = "BTreeMap<Hex, Vec<DisplayFromStr>>")]
376-
/// pub collection: BTreeMap<Vec<u8>, Vec<u128>>,
378+
/// #[serde_as(as = "BTreeMap<DisplayFromStr, Vec<DisplayFromStr>>")]
379+
/// pub collection: BTreeMap<u128, Vec<u128>>,
377380
///
378381
/// #[serde_as(as = "JsonString")]
379382
/// pub json_string: serde_json::Value,
@@ -382,18 +385,16 @@ compile_error!(
382385
/// # assert_eq!(
383386
/// # serde_json::to_value(&MyStruct {
384387
/// # amount: u128::MAX,
385-
/// # hex_bytes: vec![0x1a, 0x2b, 0x3c],
386-
/// # base64_bytes: vec![1, 2, 3],
387-
/// # collection: [(vec![0x1a, 0x2b, 0x3c], vec![u128::MAX])].into(),
388+
/// # base64_bytes: Base64VecU8::from(vec![1, 2, 3]),
389+
/// # collection: [(u128::MAX, vec![100, 200, u128::MAX])].into(),
388390
/// # json_string: json!({"key": "value"}),
389391
/// # })
390392
/// # .unwrap(),
391393
/// # json!({
392394
/// # "amount": "340282366920938463463374607431768211455",
393-
/// # "hex_bytes": "1a2b3c",
394395
/// # "base64_bytes": "AQID",
395396
/// # "collection": {
396-
/// # "1a2b3c": ["340282366920938463463374607431768211455"],
397+
/// # "340282366920938463463374607431768211455": ["100", "200", "340282366920938463463374607431768211455"],
397398
/// # },
398399
/// # "json_string": "{\"key\":\"value\"}",
399400
/// # })
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Test for Vec<u128> field with serde_as using near-sdk's U128 type
2+
// Demonstrates that u128::MAX values in a vector can be properly serialized as strings,
3+
// since JSON doesn't support integers larger than 2^53-1
4+
5+
use near_sdk::{json_types::U128, near, serde_with::FromInto};
6+
7+
#[near(serializers = [json])]
8+
#[derive(Debug, Clone, PartialEq)]
9+
pub struct AmountWrapper {
10+
#[serde_as(as = "Vec<FromInto<U128>>")]
11+
pub amounts: Vec<u128>,
12+
}
13+
14+
#[test]
15+
fn test_vec_u128_with_serde_as_u128() {
16+
// u128::MAX = 340282366920938463463374607431768211455
17+
// This value cannot be represented in JSON as a number (exceeds 2^53-1)
18+
// Using serde_as with Vec<FromInto<U128>> serializes each element as a string
19+
20+
let wrapper = AmountWrapper { amounts: vec![0, 12345, u128::MAX / 2, u128::MAX] };
21+
let json = serde_json::to_string(&wrapper).unwrap();
22+
println!("Serialized Vec<u128>: {}", json);
23+
24+
let expected = r#"{"amounts":["0","12345","170141183460469231731687303715884105727","340282366920938463463374607431768211455"]}"#;
25+
assert_eq!(
26+
json, expected,
27+
"Vec<u128> elements must be serialized as strings to avoid precision loss"
28+
);
29+
30+
let deserialized: AmountWrapper = serde_json::from_str(&json).unwrap();
31+
assert_eq!(deserialized.amounts[0], 0, "First element should be 0");
32+
assert_eq!(deserialized.amounts[1], 12345, "Second element should be 12345");
33+
assert_eq!(deserialized.amounts[2], u128::MAX / 2, "Third element should be u128::MAX / 2");
34+
assert_eq!(deserialized.amounts[3], u128::MAX, "Fourth element should be u128::MAX");
35+
assert_eq!(wrapper, deserialized, "Round-trip serialization must preserve all values");
36+
}
37+
38+
#[cfg(feature = "abi")]
39+
#[test]
40+
fn test_vec_u128_schema_generation() {
41+
use near_sdk::schemars::schema_for;
42+
let schema = schema_for!(AmountWrapper);
43+
let schema_json = serde_json::to_string_pretty(&schema).unwrap();
44+
45+
println!("Generated schema:\n{}", schema_json);
46+
47+
let schema_value: serde_json::Value = serde_json::from_str(&schema_json).unwrap();
48+
let items_type = &schema_value["properties"]["amounts"]["items"]["type"];
49+
50+
assert_eq!(
51+
items_type.as_str().unwrap(),
52+
"string",
53+
"Vec<u128> elements must have type 'string' in schema (not 'integer') to match serialization behavior"
54+
);
55+
}

0 commit comments

Comments
 (0)