Skip to content

Commit 3be477b

Browse files
authored
Speed up get_json_pointer (#678)
The regex only needs to get called when there's a double quote somewhere in the input. Benchmarks below are for the two cases: When there's no double quote, we skip the regex and go fast. When there is a double quote, we run the regex and go slow. test bench_get_json_pointer ... bench: 96 ns/iter (+/- 1) test bench_get_json_pointer_with_map ... bench: 518 ns/iter (+/- 18)
1 parent f4c855a commit 3be477b

File tree

3 files changed

+38
-6
lines changed

3 files changed

+38
-6
lines changed

benches/json_pointer.rs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#![feature(test)]
2+
extern crate tera;
3+
extern crate test;
4+
5+
#[bench]
6+
fn bench_get_json_pointer(b: &mut test::Bencher) {
7+
b.iter(|| tera::get_json_pointer("foo.bar.baz"))
8+
}
9+
10+
#[bench]
11+
fn bench_get_json_pointer_with_map(b: &mut test::Bencher) {
12+
b.iter(|| tera::get_json_pointer("foo[\"http://example.com/\"].bar.baz"))
13+
}

src/context.rs

+22-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use std::collections::BTreeMap;
21
use std::io::Write;
2+
use std::{collections::BTreeMap, iter};
33

44
use serde::ser::Serialize;
55
use serde_json::value::{to_value, Map, Value};
@@ -212,12 +212,17 @@ pub fn get_json_pointer(key: &str) -> String {
212212
lazy_static::lazy_static! {
213213
// Split the key into dot-separated segments, respecting quoted strings as single units
214214
// to fix https://github.com/Keats/tera/issues/590
215-
static ref JSON_POINTER_REGEX: regex::Regex = regex::Regex::new("\"[^\"]*\"|[^.]+").unwrap();
215+
static ref JSON_POINTER_REGEX: regex::Regex = regex::Regex::new(r#""[^"]*"|[^.]+"#).unwrap();
216216
}
217217

218-
let mut segments = vec![""];
219-
segments.extend(JSON_POINTER_REGEX.find_iter(key).map(|mat| mat.as_str().trim_matches('"')));
220-
segments.join("/")
218+
if key.find('"').is_some() {
219+
let segments: Vec<&str> = iter::once("")
220+
.chain(JSON_POINTER_REGEX.find_iter(key).map(|mat| mat.as_str().trim_matches('"')))
221+
.collect();
222+
segments.join("/")
223+
} else {
224+
["/", &key.replace(".", "/")].join("")
225+
}
221226
}
222227

223228
#[cfg(test)]
@@ -227,6 +232,18 @@ mod tests {
227232
use serde_json::json;
228233
use std::collections::HashMap;
229234

235+
#[test]
236+
fn test_get_json_pointer() {
237+
assert_eq!(get_json_pointer(""), "/");
238+
assert_eq!(get_json_pointer("foo"), "/foo");
239+
assert_eq!(get_json_pointer("foo.bar.baz"), "/foo/bar/baz");
240+
assert_eq!(get_json_pointer(r#"foo["bar"].baz"#), r#"/foo["bar"]/baz"#);
241+
assert_eq!(
242+
get_json_pointer(r#"foo["bar"].baz["qux"].blub"#),
243+
r#"/foo["bar"]/baz["qux"]/blub"#
244+
);
245+
}
246+
230247
#[test]
231248
fn can_extend_context() {
232249
let mut target = Context::new();

src/lib.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ mod utils;
2222

2323
// Library exports.
2424

25-
// Template is meant to be used internally only but is exported for test/bench.
2625
pub use crate::builtins::filters::Filter;
2726
pub use crate::builtins::functions::Function;
2827
pub use crate::builtins::testers::Test;
2928
pub use crate::context::Context;
3029
pub use crate::errors::{Error, ErrorKind, Result};
30+
// Template and get_json_pointer are meant to be used internally only but is exported for test/bench.
31+
#[doc(hidden)]
32+
pub use crate::context::get_json_pointer;
3133
#[doc(hidden)]
3234
pub use crate::template::Template;
3335
pub use crate::tera::Tera;

0 commit comments

Comments
 (0)