Skip to content

stdlib: add json. #1148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
257 changes: 257 additions & 0 deletions src/lib/json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
// Rust JSON serialization library
// Copyright (c) 2011 Google Inc.

import float;
import map;
import option;
import option::{some, none};
import str;
import vec;

export json;
export to_str;
export from_str;

export num;
export string;
export boolean;
export list;
export dict;

/*
Tag: json

Represents a json value.
*/
tag json {
/* Variant: num */
num(float);
/* Variant: string */
string(str);
/* Variant: boolean */
boolean(bool);
/* Variant: list */
list(@[json]);
/* Variant: dict */
dict(map::hashmap<str,json>);
}

/*
Function: to_str

Serializes a json value into a string.
*/
fn to_str(j: json) -> str {
alt j {
num(f) { float::to_str(f, 6u) }
string(s) { #fmt["\"%s\"", s] } // XXX: escape
boolean(true) { "true" }
boolean(false) { "false" }
list(@js) {
str::concat(["[",
str::connect(
vec::map::<json,str>({ |e| to_str(e) }, js),
", "),
"]"])
}
dict(m) {
let parts = [];
m.items({ |k, v|
vec::grow(parts, 1u,
str::concat(["\"", k, "\": ", to_str(v)])
)
});
str::concat(["{ ", str::connect(parts, ", "), " }"])
}
}
}

fn rest(s: str) -> str {
assert(str::char_len(s) >= 1u);
str::char_slice(s, 1u, str::char_len(s))
}

fn from_str_str(s: str) -> (option::t<json>, str) {
let pos = 0u;
let len = str::byte_len(s);
let escape = false;
let res = "";

alt str::char_at(s, 0u) {
'"' { pos = 1u; }
_ { ret (none, s); }
}

while (pos < len) {
let chr = str::char_range_at(s, pos);
let c = chr.ch;
pos = chr.next;
if (escape) {
res = res + str::from_char(c);
escape = false;
cont;
}
if (c == '\\') {
escape = true;
cont;
} else if (c == '"') {
ret (some(string(res)), str::char_slice(s, pos, str::char_len(s)));
}
res = res + str::from_char(c);
}

ret (none, s);
}

fn from_str_list(s: str) -> (option::t<json>, str) {
if str::char_at(s, 0u) != '[' { ret (none, s); }
let s0 = str::trim_left(rest(s));
let vals = [];
if str::is_empty(s0) { ret (none, s0); }
if str::char_at(s0, 0u) == ']' { ret (some(list(@[])), rest(s0)); }
while str::is_not_empty(s0) {
s0 = str::trim_left(s0);
let (next, s1) = from_str_helper(s0);
s0 = s1;
alt next {
some(j) { vec::grow(vals, 1u, j); }
none { ret (none, s0); }
}
s0 = str::trim_left(s0);
if str::is_empty(s0) { ret (none, s0); }
alt str::char_at(s0, 0u) {
',' { }
']' { ret (some(list(@vals)), rest(s0)); }
_ { ret (none, s0); }
}
s0 = rest(s0);
}
ret (none, s0);
}

fn from_str_dict(s: str) -> (option::t<json>, str) {
if str::char_at(s, 0u) != '{' { ret (none, s); }
let s0 = str::trim_left(rest(s));
let vals = map::new_str_hash::<json>();
if str::is_empty(s0) { ret (none, s0); }
if str::char_at(s0, 0u) == '}' { ret (some(dict(vals)), rest(s0)); }
while str::is_not_empty(s0) {
s0 = str::trim_left(s0);
let (next, s1) = from_str_helper(s0); // key
let key = "";
s0 = s1;
alt next {
some(string(k)) { key = k; }
_ { ret (none, s0); }
}
s0 = str::trim_left(s0);
if str::is_empty(s0) { ret (none, s0); }
if str::char_at(s0, 0u) != ':' { ret (none, s0); }
s0 = str::trim_left(rest(s0));
let (next, s1) = from_str_helper(s0); // value
s0 = s1;
alt next {
some(j) { vals.insert(key, j); }
_ { ret (none, s0); }
}
s0 = str::trim_left(s0);
if str::is_empty(s0) { ret (none, s0); }
alt str::char_at(s0, 0u) {
',' { }
'}' { ret (some(dict(vals)), rest(s0)); }
_ { ret (none, s0); }
}
s0 = str::trim_left(rest(s0));
}
(none, s)
}

fn from_str_float(s: str) -> (option::t<json>, str) {
let pos = 0u;
let len = str::byte_len(s);
let res = 0f;
let neg = 1.f;

alt str::char_at(s, 0u) {
'-' {
neg = -1.f;
pos = 1u;
}
'+' {
pos = 1u;
}
'0' to '9' | '.' { }
_ { ret (none, s); }
}

while (pos < len) {
let opos = pos;
let chr = str::char_range_at(s, pos);
let c = chr.ch;
pos = chr.next;
alt c {
'0' to '9' {
res = res * 10f;
res += ((c as int) - ('0' as int)) as float;
}
'.' { break; }
_ { ret (some(num(neg * res)),
str::char_slice(s, opos, str::char_len(s))); }
}
}

if pos == len {
ret (some(num(neg * res)), str::char_slice(s, pos, str::char_len(s)));
}

let dec = 1.f;
while (pos < len) {
let opos = pos;
let chr = str::char_range_at(s, pos);
let c = chr.ch;
pos = chr.next;
alt c {
'0' to '9' {
dec /= 10.f;
res += (((c as int) - ('0' as int)) as float) * dec;
}
_ { ret (some(num(neg * res)),
str::char_slice(s, opos, str::char_len(s))); }
}
}
ret (some(num(neg * res)), str::char_slice(s, pos, str::char_len(s)));
}

fn from_str_bool(s: str) -> (option::t<json>, str) {
if (str::starts_with(s, "true")) {
(some(boolean(true)), str::slice(s, 4u, str::byte_len(s)))
} else if (str::starts_with(s, "false")) {
(some(boolean(false)), str::slice(s, 5u, str::byte_len(s)))
} else {
(none, s)
}
}

fn from_str_helper(s: str) -> (option::t<json>, str) {
let s = str::trim_left(s);
if str::is_empty(s) { ret (none, s); }
let start = str::char_at(s, 0u);
alt start {
'"' { from_str_str(s) }
'[' { from_str_list(s) }
'{' { from_str_dict(s) }
'0' to '9' | '-' | '+' | '.' { from_str_float(s) }
't' | 'f' { from_str_bool(s) }
_ { ret (none, s); }
}
}

/*
Function: from_str

Deserializes a json value from a string.
*/
fn from_str(s: str) -> option::t<json> {
let (j, _) = from_str_helper(s);
j
}
3 changes: 2 additions & 1 deletion src/lib/std.rc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export aio, comm, fs, io, net, run, sio, sys, task;
export ctypes, either, option, result, util;
export bitv, deque, fun_treemap, list, map, smallintmap, sort, treemap, ufind;
export rope;
export ebml, dbg, getopts, math, rand, sha1, term, time, unsafe;
export ebml, dbg, getopts, json, math, rand, sha1, term, time, unsafe;
export extfmt, test;
// FIXME: generic_os and os_fs shouldn't be exported
export generic_os, os, os_fs;
Expand Down Expand Up @@ -74,6 +74,7 @@ mod ufind;
mod ebml;
mod dbg;
mod getopts;
mod json;
mod math;
mod rand;
mod sha1;
Expand Down
48 changes: 48 additions & 0 deletions src/test/stdtest/json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use std;
import std::json::*;
import std::option::{none, some};

#[test]
fn test_from_str_num() {
assert(from_str("3") == some(num(3f)));
assert(from_str("3.1") == some(num(3.1f)));
assert(from_str("-1.2") == some(num(-1.2f)));
assert(from_str(".4") == some(num(0.4f)));
}

#[test]
fn test_from_str_str() {
assert(from_str("\"foo\"") == some(string("foo")));
assert(from_str("\"\\\"\"") == some(string("\"")));
assert(from_str("\"lol") == none);
}

#[test]
fn test_from_str_bool() {
assert(from_str("true") == some(boolean(true)));
assert(from_str("false") == some(boolean(false)));
assert(from_str("truz") == none);
}

#[test]
fn test_from_str_list() {
assert(from_str("[]") == some(list(@[])));
assert(from_str("[true]") == some(list(@[boolean(true)])));
assert(from_str("[3, 1]") == some(list(@[num(3f), num(1f)])));
assert(from_str("[2, [4, 1]]") ==
some(list(@[num(2f), list(@[num(4f), num(1f)])])));
assert(from_str("[2, ]") == none);
assert(from_str("[5, ") == none);
assert(from_str("[6 7]") == none);
assert(from_str("[3") == none);
}

#[test]
fn test_from_str_dict() {
assert(from_str("{}") != none);
assert(from_str("{\"a\": 3}") != none);
assert(from_str("{\"a\": }") == none);
assert(from_str("{\"a\" }") == none);
assert(from_str("{\"a\"") == none);
assert(from_str("{") == none);
}
13 changes: 7 additions & 6 deletions src/test/stdtest/stdtest.rc
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ mod char;
mod comm;
mod deque;
mod either;
mod float;
mod fs;
mod getopts;
mod int;
mod io;
mod vec;
mod json;
mod list;
mod map;
mod treemap;
mod math;
mod net;
mod option;
mod os;
Expand All @@ -22,18 +23,18 @@ mod ptr;
mod qsort3;
mod qsort;
mod rand;
mod result;
mod rope;
mod run;
mod sha1;
mod sort;
mod str;
mod sys;
mod task;
mod test;
mod treemap;
mod uint;
mod float;
mod math;
mod result;
mod rope;
mod vec;

// Local Variables:
// mode: rust
Expand Down