use yo_common::{Code, Error, Result};
use yo_vector::Signature;
const BAD_FILTER: &str = "invalid FILTER expression";
const DEPTH: usize = 32;
#[derive(Debug)]
pub(super) struct Filter {
root: Node,
}
impl Filter {
pub(super) fn parse(src: &[u8]) -> Result<Filter> {
let toks = lex(src)?;
let mut p = Parser { toks: &toks, at: 0 };
let root = p.expr(0)?;
if p.at != toks.len() {
return Err(bad());
}
Ok(Filter { root })
}
pub(super) fn signature(&self) -> Signature {
let mut sig = Signature::default();
require(&self.root, &mut sig);
sig
}
pub(super) fn matches(&self, attr: Option<&[u8]>) -> bool {
truth(eval(&self.root, attr.unwrap_or(b"")))
}
}
pub(super) fn tag(attr: &[u8]) -> u64 {
let mut sig = Signature::default();
let mut name = Vec::new();
let mut value = Vec::new();
let mut scan = Json::new(attr);
while let Some(()) = scan.next_field(&mut name) {
if scan.string_value(&mut value) {
sig.insert_bytes(&name, &value);
}
}
sig.bits()
}
fn require(node: &Node, sig: &mut Signature) {
match node {
Node::Bin(Op::And, pair) => {
require(&pair.0, sig);
require(&pair.1, sig);
}
Node::Bin(Op::Eq, pair) => match (&pair.0, &pair.1) {
(Node::Field(f), Node::Text(t)) | (Node::Text(t), Node::Field(f)) => {
sig.insert_bytes(f, t);
}
_ => {}
},
_ => {}
}
}
#[derive(Debug)]
enum Node {
Field(Box<[u8]>),
Num(f64),
Text(Box<[u8]>),
Bool(bool),
List(Box<[Node]>),
Not(Box<Node>),
Neg(Box<Node>),
Bin(Op, Box<(Node, Node)>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Op {
Or,
And,
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
In,
Add,
Sub,
Mul,
Div,
Rem,
Pow,
}
#[derive(Debug, Clone, Copy)]
enum Val<'a> {
Null,
Bool(bool),
Num(f64),
Text(Text<'a>),
}
#[derive(Debug, Clone, Copy)]
struct Text<'a> {
raw: &'a [u8],
escaped: bool,
}
fn text_cmp(a: Text<'_>, b: Text<'_>) -> core::cmp::Ordering {
match (a.escaped, b.escaped) {
(false, false) => a.raw.cmp(b.raw),
_ => Decode::new(a).cmp(Decode::new(b)),
}
}
struct Decode<'a> {
raw: &'a [u8],
at: usize,
held: [u8; 4],
have: usize,
took: usize,
}
impl<'a> Decode<'a> {
fn new(t: Text<'a>) -> Decode<'a> {
Decode {
raw: t.raw,
at: 0,
held: [0; 4],
have: 0,
took: 0,
}
}
}
impl Iterator for Decode<'_> {
type Item = u8;
fn next(&mut self) -> Option<u8> {
if self.took < self.have {
let byte = self.held[self.took];
self.took += 1;
return Some(byte);
}
let byte = *self.raw.get(self.at)?;
self.at += 1;
if byte != b'\\' {
return Some(byte);
}
let esc = *self.raw.get(self.at)?;
self.at += 1;
let plain = match esc {
b'n' => b'\n',
b'r' => b'\r',
b't' => b'\t',
b'b' => 0x08,
b'f' => 0x0c,
b'u' => {
let c = self.unicode()?;
let mut buf = [0u8; 4];
let s = c.encode_utf8(&mut buf);
self.held = [0; 4];
self.held[..s.len()].copy_from_slice(s.as_bytes());
self.have = s.len();
self.took = 1;
return Some(self.held[0]);
}
other => other,
};
Some(plain)
}
}
impl Decode<'_> {
fn unicode(&mut self) -> Option<char> {
let first = self.hex4()?;
let point = if (0xd800..0xdc00).contains(&first) {
match self.pair() {
Some(low) => 0x1_0000 + ((first - 0xd800) << 10) + (low - 0xdc00),
None => return Some(char::REPLACEMENT_CHARACTER),
}
} else {
first
};
Some(char::from_u32(point).unwrap_or(char::REPLACEMENT_CHARACTER))
}
fn pair(&mut self) -> Option<u32> {
if self.raw.get(self.at..self.at + 2)? != b"\\u" {
return None;
}
let mark = self.at;
self.at += 2;
match self.hex4() {
Some(low) if (0xdc00..0xe000).contains(&low) => Some(low),
_ => {
self.at = mark;
None
}
}
}
fn hex4(&mut self) -> Option<u32> {
let digits = self.raw.get(self.at..self.at + 4)?;
self.at += 4;
let mut got = 0u32;
for d in digits {
got = got * 16 + char::from(*d).to_digit(16)?;
}
Some(got)
}
}
fn truth(v: Val<'_>) -> bool {
match v {
Val::Bool(b) => b,
Val::Num(n) => n != 0.0,
Val::Null | Val::Text(_) => false,
}
}
fn eval<'a>(node: &'a Node, attr: &'a [u8]) -> Val<'a> {
match node {
Node::Field(name) => field(attr, name),
Node::Num(n) => Val::Num(*n),
Node::Text(t) => Val::Text(Text {
raw: t,
escaped: false,
}),
Node::Bool(b) => Val::Bool(*b),
Node::List(_) => Val::Null,
Node::Not(inner) => Val::Bool(!truth(eval(inner, attr))),
Node::Neg(inner) => match eval(inner, attr) {
Val::Num(n) => Val::Num(-n),
_ => Val::Null,
},
Node::Bin(Op::And, pair) => {
Val::Bool(truth(eval(&pair.0, attr)) && truth(eval(&pair.1, attr)))
}
Node::Bin(Op::Or, pair) => {
Val::Bool(truth(eval(&pair.0, attr)) || truth(eval(&pair.1, attr)))
}
Node::Bin(Op::In, pair) => Val::Bool(contains(&pair.1, eval(&pair.0, attr), attr)),
Node::Bin(op, pair) => binary(*op, eval(&pair.0, attr), eval(&pair.1, attr)),
}
}
fn contains<'a>(list: &'a Node, want: Val<'a>, attr: &'a [u8]) -> bool {
let Node::List(items) = list else {
return false;
};
items
.iter()
.any(|item| matches!(binary(Op::Eq, want, eval(item, attr)), Val::Bool(true)))
}
fn binary<'a>(op: Op, a: Val<'a>, b: Val<'a>) -> Val<'a> {
use core::cmp::Ordering;
let order = match (a, b) {
(Val::Num(x), Val::Num(y)) => Some(x.total_cmp(&y)),
(Val::Text(x), Val::Text(y)) => Some(text_cmp(x, y)),
(Val::Bool(x), Val::Bool(y)) => Some(x.cmp(&y)),
_ => None,
};
match op {
Op::Eq => return Val::Bool(order == Some(Ordering::Equal)),
Op::Ne => return Val::Bool(order != Some(Ordering::Equal)),
_ => {}
}
let Some(order) = order else {
return Val::Bool(false);
};
match op {
Op::Lt => return Val::Bool(order == Ordering::Less),
Op::Le => return Val::Bool(order != Ordering::Greater),
Op::Gt => return Val::Bool(order == Ordering::Greater),
Op::Ge => return Val::Bool(order != Ordering::Less),
_ => {}
}
let (Val::Num(x), Val::Num(y)) = (a, b) else {
return Val::Null;
};
let got = match op {
Op::Add => x + y,
Op::Sub => x - y,
Op::Mul => x * y,
Op::Div => x / y,
Op::Rem => x % y,
Op::Pow => x.powf(y),
Op::Or | Op::And | Op::In | Op::Eq | Op::Ne | Op::Lt | Op::Le | Op::Gt | Op::Ge => {
unreachable!("answered above")
}
};
Val::Num(got)
}
fn field<'a>(attr: &'a [u8], name: &[u8]) -> Val<'a> {
let mut scan = Json::new(attr);
let mut key = Vec::new();
while scan.next_field(&mut key).is_some() {
if key == name {
return scan.value();
}
scan.skip_value();
}
Val::Null
}
struct Json<'a> {
src: &'a [u8],
at: usize,
started: bool,
}
impl<'a> Json<'a> {
fn new(src: &'a [u8]) -> Json<'a> {
Json {
src,
at: 0,
started: false,
}
}
fn next_field(&mut self, into: &mut Vec<u8>) -> Option<()> {
if !self.started {
self.started = true;
self.space();
self.eat(b'{')?;
} else {
self.space();
match self.peek()? {
b',' => self.at += 1,
_ => return None,
}
}
self.space();
if self.peek()? == b'}' {
return None;
}
let key = self.text()?;
into.clear();
into.extend(Decode::new(key));
self.space();
self.eat(b':')?;
self.space();
Some(())
}
fn value(&mut self) -> Val<'a> {
match self.peek() {
Some(b'"') => match self.text() {
Some(t) => Val::Text(t),
None => Val::Null,
},
Some(b't') if self.word(b"true") => Val::Bool(true),
Some(b'f') if self.word(b"false") => Val::Bool(false),
Some(b'n') if self.word(b"null") => Val::Null,
Some(c) if c == b'-' || c.is_ascii_digit() => match self.number() {
Some(n) => Val::Num(n),
None => Val::Null,
},
_ => Val::Null,
}
}
fn string_value(&mut self, into: &mut Vec<u8>) -> bool {
if self.peek() != Some(b'"') {
self.skip_value();
return false;
}
let Some(text) = self.text() else {
return false;
};
into.clear();
into.extend(Decode::new(text));
true
}
fn skip_value(&mut self) {
self.space();
match self.peek() {
Some(b'"') => {
let _ = self.text();
}
Some(b'{' | b'[') => {
let mut depth = 0usize;
loop {
self.space();
let Some(c) = self.peek() else { return };
match c {
b'"' => {
if self.text().is_none() {
return;
}
}
b'{' | b'[' => {
depth += 1;
self.at += 1;
}
b'}' | b']' => {
depth -= 1;
self.at += 1;
if depth == 0 {
return;
}
}
_ => self.at += 1,
}
}
}
_ => {
while let Some(c) = self.peek() {
if matches!(c, b',' | b'}' | b']') {
return;
}
self.at += 1;
}
}
}
}
fn text(&mut self) -> Option<Text<'a>> {
self.eat(b'"')?;
let from = self.at;
let mut escaped = false;
loop {
let c = *self.src.get(self.at)?;
self.at += 1;
match c {
b'"' => break,
b'\\' => {
escaped = true;
self.at += 1;
}
_ => {}
}
}
Some(Text {
raw: self.src.get(from..self.at - 1)?,
escaped,
})
}
fn number(&mut self) -> Option<f64> {
let from = self.at;
while let Some(c) = self.peek() {
if c.is_ascii_digit() || matches!(c, b'-' | b'+' | b'.' | b'e' | b'E') {
self.at += 1;
} else {
break;
}
}
core::str::from_utf8(&self.src[from..self.at])
.ok()?
.parse()
.ok()
}
fn word(&mut self, want: &[u8]) -> bool {
if self.src[self.at..].starts_with(want) {
self.at += want.len();
return true;
}
false
}
fn peek(&self) -> Option<u8> {
self.src.get(self.at).copied()
}
fn eat(&mut self, want: u8) -> Option<()> {
if self.peek()? != want {
return None;
}
self.at += 1;
Some(())
}
fn space(&mut self) {
while matches!(self.peek(), Some(b' ' | b'\t' | b'\n' | b'\r')) {
self.at += 1;
}
}
}
#[derive(Debug, PartialEq)]
enum Tok {
Field(Box<[u8]>),
Num(f64),
Text(Box<[u8]>),
Bool(bool),
Open,
Close,
OpenList,
CloseList,
Comma,
Not,
Op(Op),
}
fn bad() -> Error {
Error::new(Code::Invalid, BAD_FILTER)
}
fn lex(src: &[u8]) -> Result<Vec<Tok>> {
let mut out = Vec::new();
let mut at = 0;
while at < src.len() {
let c = src[at];
match c {
b' ' | b'\t' | b'\n' | b'\r' => at += 1,
b'(' => {
out.push(Tok::Open);
at += 1;
}
b')' => {
out.push(Tok::Close);
at += 1;
}
b'[' => {
out.push(Tok::OpenList);
at += 1;
}
b']' => {
out.push(Tok::CloseList);
at += 1;
}
b',' => {
out.push(Tok::Comma);
at += 1;
}
b'.' => {
let from = at + 1;
at = from;
while at < src.len() && (src[at].is_ascii_alphanumeric() || src[at] == b'_') {
at += 1;
}
if at == from {
return Err(bad());
}
out.push(Tok::Field(src[from..at].into()));
}
b'"' | b'\'' => {
let (text, next) = literal(src, at)?;
out.push(Tok::Text(text));
at = next;
}
b'0'..=b'9' => {
let from = at;
while at < src.len()
&& (src[at].is_ascii_digit() || matches!(src[at], b'.' | b'e' | b'E'))
{
if matches!(src[at], b'e' | b'E') {
let sign = at + 1 < src.len() && matches!(src[at + 1], b'+' | b'-');
let digit = at + 1 + usize::from(sign);
if digit >= src.len() || !src[digit].is_ascii_digit() {
break;
}
at += 1 + usize::from(sign);
}
at += 1;
}
let n: f64 = core::str::from_utf8(&src[from..at])
.map_err(|_| bad())?
.parse()
.map_err(|_| bad())?;
out.push(Tok::Num(n));
}
b'a'..=b'z' | b'A'..=b'Z' | b'_' => {
let from = at;
while at < src.len() && (src[at].is_ascii_alphanumeric() || src[at] == b'_') {
at += 1;
}
out.push(match &src[from..at] {
b"and" => Tok::Op(Op::And),
b"or" => Tok::Op(Op::Or),
b"not" => Tok::Not,
b"in" => Tok::Op(Op::In),
b"true" => Tok::Bool(true),
b"false" => Tok::Bool(false),
_ => return Err(bad()),
});
}
_ => {
let (tok, next) = symbol(src, at)?;
out.push(tok);
at = next;
}
}
}
Ok(out)
}
fn literal(src: &[u8], from: usize) -> Result<(Box<[u8]>, usize)> {
let quote = src[from];
let mut at = from + 1;
let body = loop {
let c = *src.get(at).ok_or_else(bad)?;
if c == quote {
break &src[from + 1..at];
}
at += if c == b'\\' { 2 } else { 1 };
};
let text = Text {
raw: body,
escaped: body.contains(&b'\\'),
};
Ok((Decode::new(text).collect::<Vec<u8>>().into(), at + 1))
}
fn symbol(src: &[u8], at: usize) -> Result<(Tok, usize)> {
let two = src.get(at..at + 2);
let tok = match two {
Some(b"==") => Some(Tok::Op(Op::Eq)),
Some(b"!=") => Some(Tok::Op(Op::Ne)),
Some(b"<=") => Some(Tok::Op(Op::Le)),
Some(b">=") => Some(Tok::Op(Op::Ge)),
Some(b"&&") => Some(Tok::Op(Op::And)),
Some(b"||") => Some(Tok::Op(Op::Or)),
Some(b"**") => Some(Tok::Op(Op::Pow)),
_ => None,
};
if let Some(tok) = tok {
return Ok((tok, at + 2));
}
let one = match src[at] {
b'<' => Tok::Op(Op::Lt),
b'>' => Tok::Op(Op::Gt),
b'!' => Tok::Not,
b'+' => Tok::Op(Op::Add),
b'-' => Tok::Op(Op::Sub),
b'*' => Tok::Op(Op::Mul),
b'/' => Tok::Op(Op::Div),
b'%' => Tok::Op(Op::Rem),
_ => return Err(bad()),
};
Ok((one, at + 1))
}
struct Parser<'a> {
toks: &'a [Tok],
at: usize,
}
fn power(op: Op) -> (u8, u8) {
match op {
Op::Or => (1, 2),
Op::And => (3, 4),
Op::Eq | Op::Ne | Op::Lt | Op::Le | Op::Gt | Op::Ge | Op::In => (5, 6),
Op::Add | Op::Sub => (7, 8),
Op::Mul | Op::Div | Op::Rem => (9, 10),
Op::Pow => (12, 11),
}
}
impl Parser<'_> {
fn expr(&mut self, least: u8) -> Result<Node> {
self.expr_at(least, 0)
}
fn expr_at(&mut self, least: u8, depth: usize) -> Result<Node> {
if depth > DEPTH {
return Err(bad());
}
let mut left = self.unary(depth)?;
while let Some(Tok::Op(op)) = self.toks.get(self.at) {
let (lhs, rhs) = power(*op);
if lhs < least {
break;
}
let op = *op;
self.at += 1;
let right = if op == Op::In {
self.list(depth)?
} else {
self.expr_at(rhs, depth + 1)?
};
left = Node::Bin(op, Box::new((left, right)));
}
Ok(left)
}
fn unary(&mut self, depth: usize) -> Result<Node> {
if depth > DEPTH {
return Err(bad());
}
match self.toks.get(self.at) {
Some(Tok::Not) => {
self.at += 1;
Ok(Node::Not(Box::new(self.unary(depth + 1)?)))
}
Some(Tok::Op(Op::Sub)) => {
self.at += 1;
Ok(Node::Neg(Box::new(self.unary(depth + 1)?)))
}
_ => self.value(depth),
}
}
fn value(&mut self, depth: usize) -> Result<Node> {
let tok = self.toks.get(self.at).ok_or_else(bad)?;
self.at += 1;
match tok {
Tok::Field(name) => Ok(Node::Field(name.clone())),
Tok::Num(n) => Ok(Node::Num(*n)),
Tok::Text(t) => Ok(Node::Text(t.clone())),
Tok::Bool(b) => Ok(Node::Bool(*b)),
Tok::Open => {
let inner = self.expr_at(0, depth + 1)?;
match self.toks.get(self.at) {
Some(Tok::Close) => {
self.at += 1;
Ok(inner)
}
_ => Err(bad()),
}
}
Tok::OpenList => {
self.at -= 1;
self.list(depth)
}
_ => Err(bad()),
}
}
fn list(&mut self, depth: usize) -> Result<Node> {
if depth > DEPTH {
return Err(bad());
}
match self.toks.get(self.at) {
Some(Tok::OpenList) => self.at += 1,
_ => return Err(bad()),
}
let mut items = Vec::new();
if self.toks.get(self.at) == Some(&Tok::CloseList) {
self.at += 1;
return Ok(Node::List(items.into()));
}
loop {
items.push(self.expr_at(1, depth + 1)?);
match self.toks.get(self.at) {
Some(Tok::Comma) => self.at += 1,
Some(Tok::CloseList) => {
self.at += 1;
return Ok(Node::List(items.into()));
}
_ => return Err(bad()),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hit(expr: &str, attr: &str) -> bool {
Filter::parse(expr.as_bytes())
.expect("the expression is one")
.matches(Some(attr.as_bytes()))
}
fn refused(expr: &str) -> bool {
Filter::parse(expr.as_bytes()).is_err()
}
#[test]
fn an_expression_reads_the_way_it_looks() {
let doc = r#"{"a":2,"b":3,"lang":"en"}"#;
assert!(hit(".a + 1 == 3", doc));
assert!(hit(".a * .b == 6", doc));
assert!(hit(".a + .b * 2 == 8", doc));
assert!(hit("(.a + .b) * 2 == 10", doc));
assert!(hit("2 ** 3 ** 2 == 512", doc));
assert!(hit("-.a == 0 - 2", doc));
assert!(hit(".b % .a == 1", doc));
assert!(hit(".lang == 'en' and .a == 2 or .a == 99", doc));
assert!(!hit(".lang == 'de' and (.a == 2 or .a == 99)", doc));
assert!(hit(".a == 2 && .b == 3", doc));
assert!(hit(".a == 9 || .b == 3", doc));
assert!(hit("!(.a == 9)", doc));
assert!(hit("not (.a == 9)", doc));
assert!(!hit("not .a == 9", doc));
assert!(hit("!.missing", doc));
assert!(hit(".lang in ['fr', 'en']", doc));
assert!(!hit(".lang in []", doc));
}
#[test]
fn a_field_that_is_not_there_matches_nothing() {
let doc = r#"{"year":1999,"nested":{"x":1},"list":[1,2]}"#;
assert!(!hit(".missing > 1980", doc));
assert!(!hit(".missing < 1980", doc));
assert!(!hit(".missing == 1980", doc));
assert!(hit(".missing != 1980", doc));
assert!(!hit(".nested == 1", doc));
assert!(!hit(".list == 1", doc));
assert!(hit(".missing > 1980 or .year > 1980", doc));
assert!(!hit(".missing > 1980 and .year > 1980", doc));
assert!(!hit(".missing + 1 == 1", doc));
assert!(!hit(".year == 1999", "not json"));
assert!(!hit(".year == 1999", "{\"year\":"));
assert!(!Filter::parse(b".year == 1999").unwrap().matches(None));
}
#[test]
fn the_types_do_not_coerce() {
let doc = r#"{"year":1999,"open":true,"tag":"7"}"#;
assert!(!hit(".year == '1999'", doc));
assert!(!hit(".tag == 7", doc));
assert!(hit(".tag == '7'", doc));
assert!(hit(".open == true", doc));
assert!(hit(".open", doc));
assert!(!hit(".open == 1", doc));
assert!(hit(".year", doc));
assert!(!hit(".tag", doc));
}
#[test]
fn an_escape_is_compared_by_what_it_stands_for() {
assert!(hit(r".a == 'x\ny'", r#"{"a":"x\ny"}"#));
assert!(hit(".a == 'x\ny'", r#"{"a":"x\ny"}"#));
assert!(!hit(r".a == 'x\ny'", r#"{"a":"xny"}"#));
assert!(hit(".a == 'é'", r#"{"a":"é"}"#));
assert!(hit(r".a == 'é'", r#"{"a":"é"}"#));
assert!(hit(".a == '😀'", r#"{"a":"😀"}"#));
assert!(hit(".ab == 1", r#"{"ab":1}"#));
assert!(hit(".a > 'abb'", r#"{"a":"abc"}"#));
}
#[test]
fn the_signature_carries_only_what_every_match_needs() {
let want = |expr: &str| {
Filter::parse(expr.as_bytes())
.expect("the expression is one")
.signature()
};
let of = |attr: &str| Signature::from_bits(tag(attr.as_bytes()));
let doc = of(r#"{"lang":"en","year":1999}"#);
assert!(doc.covers(want(".lang == 'en'")));
assert!(doc.covers(want(".lang == 'en' and .year > 1980")));
assert!(!doc.covers(want(".lang == 'fr'")));
assert_eq!(want(".lang == 'fr' or .lang == 'en'").bits(), 0);
assert_eq!(want("!(.lang == 'fr')").bits(), 0);
assert_eq!(want(".lang != 'fr'").bits(), 0);
assert_eq!(want(".year == 1999").bits(), 0);
assert_eq!(want(".lang in ['fr']").bits(), 0);
assert_eq!(tag(b""), 0);
assert!(!of("").covers(want(".lang == 'en'")));
assert!(doc.covers(want("'en' == .lang")));
}
#[test]
fn a_bad_expression_is_an_error_and_not_a_crash() {
assert!(refused(".k =="));
assert!(refused("=="));
assert!(refused("junk"));
assert!(refused(".a = 1"));
assert!(refused("."));
assert!(refused(".a == 'unterminated"));
assert!(refused(".a == 1)"));
assert!(refused("(.a == 1"));
assert!(refused(".a in 1"));
assert!(refused(".a in ['x'"));
assert!(refused(".a == 1 and"));
let deep = format!("{}.a == 1{}", "(".repeat(2000), ")".repeat(2000));
assert!(refused(&deep));
let chain = format!(".a == 1{}", " and .a == 1".repeat(2000));
assert!(
Filter::parse(chain.as_bytes())
.map(|f| f.matches(Some(br#"{"a":1}"#)))
.unwrap_or(true)
);
}
#[test]
fn a_number_is_read_however_it_was_spelled() {
assert!(hit(".a == 1", r#"{"a":1.0}"#));
assert!(hit(".a == 100", r#"{"a":1e2}"#));
assert!(hit(".a == 0.5", r#"{"a":5e-1}"#));
assert!(hit(".a == -3", r#"{"a":-3}"#));
assert!(hit(".a > 1e3", r#"{"a":2000}"#));
let doc = r#"{"n":{"deep":{"x":[1,{"y":"}"}]}},"a":1}"#;
assert!(hit(".a == 1", doc));
let doc = r#"{"s":"a,b}c","a":2}"#;
assert!(hit(".a == 2", doc));
assert!(hit(".a == 1", " { \"a\" : 1 } "));
}
}