Rust-Json rust: JSON implementation in Rust

json-rust

Parse and serialize JSON with ease.

Changelog - Complete Documentation - Cargo - Repository

Why?

JSON is a very loose format where anything goes - arrays can hold mixed types, object keys can change types between API calls or not include some keys under some conditions. Mapping that to idiomatic Rust structs introduces friction.

This crate intends to avoid that friction.

let parsed = json::parse(r#"

{
    "code": 200,
    "success": true,
    "payload": {
        "features": [
            "awesome",
            "easyAPI",
            "lowLearningCurve"
        ]
    }
}

"#).unwrap();

let instantiated = object!{
    // quotes on keys are optional
    "code": 200,
    success: true,
    payload: {
        features: [
            "awesome",
            "easyAPI",
            "lowLearningCurve"
        ]
    }
};

assert_eq!(parsed, instantiated);

First class citizen

Using macros and indexing, it's easy to work with the data.

let mut data = object!{
    foo: false,
    bar: null,
    answer: 42,
    list: [null, "world", true]
};

// Partial equality is implemented for most raw types:
assert!(data["foo"] == false);

// And it's type aware, `null` and `false` are different values:
assert!(data["bar"] != false);

// But you can use any Rust number types:
assert!(data["answer"] == 42);
assert!(data["answer"] == 42.0);
assert!(data["answer"] == 42isize);

// Access nested structures, arrays and objects:
assert!(data["list"][0].is_null());
assert!(data["list"][1] == "world");
assert!(data["list"][2] == true);

// Error resilient - accessing properties that don't exist yield null:
assert!(data["this"]["does"]["not"]["exist"].is_null());

// Mutate by assigning:
data["list"][0] = "Hello".into();

// Use the `dump` method to serialize the data:
assert_eq!(data.dump(), r#"{"foo":false,"bar":null,"answer":42,"list":["Hello","world",true]}"#);

// Or pretty print it out:
println!("{:#}", data);

Installation

Just add it to your Cargo.toml file:

[dependencies]
json = "*"

Then import it in your main.rs / lib.rs file:

#[macro_use]
extern crate json;

Performance and Conformance

There used to be a statement here saying that performance is not the main goal of this crate. It is definitely one of them now.

While this crate doesn't provide a way to parse JSON to native Rust structs, it does a lot to optimize its performance for DOM parsing, stringifying and manipulation. It does very well in benchmarks, in some cases it can even outperform parsing to structs.

This crate implements the standard according to the RFC 7159 and ECMA-404 documents. For the best interoperability numbers are treated stored as 64bit precision mantissa with 16 bit decimal exponent for floating point representation.

License

This crate is distributed under the terms of both the MIT license and the Apache License (Version 2.0). Choose whichever one works best for you.

See LICENSE-APACHE and LICENSE-MIT for details.

Comments

  • Allow extra characters at end
    Allow extra characters at end

    Mar 13, 2020

    It would be really helpful for my use case if there were a way to allow data after the JSON, and to report how much JSON was actually read.

    For example if I have data like this:

    {"type": "string", "length": 5}Hello
    

    It would return the JSON data, plus a length of 31 (up to the }).

    Looks like this would be easy something along the lines of this commit which I have not tested (or even compiled).

    Bit of a weird use case!

    Reply
  • `Hash` implementation
    `Hash` implementation

    Mar 17, 2020

    I need to organize JsonValues into a HashSet however I just noticed that JsonValue does not implement the Hash trait. Is there a reason for that?

    Reply
  • Order of `JsonValue::Object` entries
    Order of `JsonValue::Object` entries

    Mar 17, 2020

    The documentation is not clear about the order in which the entries of a JSON object will be iterated through the JsonValue::entries method. Is it a lexicographic order? Order of definition in the original string?

    Issue #68 is letting me think there was some kind of defined order between keys in an older version of the crate, but is now the order of definition.

    documentation 
    Reply
  • cannot match JsonValue::String
    cannot match JsonValue::String

    Mar 26, 2020

    When trying to match the elements of a Json array by their type, it seems it is not (easily) possible to do it for strings:

        let data = array!["fii", "foo", 4];
        for i in data.members() {
            println!("examining {}", i);
            match i {
                JsonValue::Number(_) => println!("  it's a number"),
                JsonValue::String(_) => println!("  it's a string"),
                _ if i.is_string()   => println!("  it's really a string"),
                _ => println!("  it's something else"),
            }
    

    While the JsonValue::Number arm works as expected, the JsonValue::String arm is never entered. Only _ if i.is_string() works. Perhaps this is the expected behaviour (I'm a rust beginner so I am not sure), but certainly it is not very intuitive...

    Reply
  • `Eq` trait implementation on `Number`
    `Eq` trait implementation on `Number`

    Mar 30, 2020

    I'm wondering why Eq is not implemented for the Number struct. At first, I thought it was because Number can also represent NaN for which we usually expect that NaN != NaN. However a rapid glance at the implementation showed me that in the implementation of PartialEq, we have NaN == NaN.

    Having Eq would be useful to use Number or even JsonValue in containers like HashSet.

    Also why do you need to represent NaN in Number? As far as I know there is no NaN in JSON, right?

    enhancement 
    Reply
  • Wish: serialize `decimal` crate objects
    Wish: serialize `decimal` crate objects

    May 21, 2020

    The decimal crate implements decimal floating point arithmetic in rust. JSON supports decimal numbers.

    It would be nice if there were an obvious way of how to serialize (and deserialize) d128 objects exactly in your json crate.

    Right now, the only obvious way is to first convert to/from f64, which loses precision.

    Reply
  • `stringify` takes ownership instead of a reference
    `stringify` takes ownership instead of a reference

    Aug 6, 2016

    Is there a reason it takes the JsonValue by value instead of by reference?

    I have a Cache that contains a JsonValue which is updated in place, and every once in a while it saves it to disk as JSON, so currently I have to clone the object which sounds like an artificial requirement.

    Other than the obvious loss of the nice json::stringify(2) and friends, I think it would be a win performance wise (at least for my case :panda_face:)

    Reply
  • Higher precision decimals
    Higher precision decimals

    Jul 18, 2016

    So I'm going back and forth on the idea of using a custom number type.

    While it's nice to be able to parse higher precision than f64, it's also pretty useless unless there is a way to use that somehow. A solution for that could be methods like:

    • as_fixed_point_u64(u16) -> Option<u64>
    • as_fixed_point_i64(u16) -> Option<i64>

    This way a number such as 12.3456 can be obtained as an integer 123456 when called with fixed point argument 4. A way to construct such numbers would be needed as well (macro?)

    Pros:

    • Much faster serializing (canada.json will be a breeze).
    • Parsing higher precision might be actually useful.

    Cons:

    • Obscuring the API, though this is already done with Object and Short types.
    • Converting floats to the decimal representation is costly. I've already done an experimental alteration of the dtoa crate to produce decimal mantissa and exponent instead of writing to string with the grisu2 algorithm. Pefromance of that is quite okay.

    Open questions:

    • Is 64 bit mantissa for precision enough?
    • Do we enable maths on the Number type and/or JsonValue?
    • Can we use feature flags to convert numer types from other crates, in particular num crate?
    enhancement 
    Reply
  • map_* methods
    map_* methods

    Jul 11, 2016

    Instead of:

    let data = object!{ foo => 10 };
    
    let Some(num) = data["foo"].as_f64() {
        data["foo"] = (num + 1).into();
    }
    
    assert_eq(data.dump(), r#"{"foo":11}"#);
    

    Do:

    let data = object!{ foo => 10 };
    
    data["foo"].map_f64(|num| num + 1);
    
    assert_eq(data.dump(), r#"{"foo":11}"#);
    

    ? or ? ?

    discussion 
    Reply
  • Parsed json data is not in the same order as the original.
    Parsed json data is not in the same order as the original.

    Jul 12, 2016

    Thanks for this crate! I'm currently writing a code generator, where you can define the API using JSON. I noticed that if a list of elements is used, the resulting parsed output is not in the same order as defined in the original JSON data, which is a bit of a problem. Example:

    What I pass to JSON parser:

    {
      "struct": {
      "var": { "type": "int", "name": "x" },
      "func": [ { "type": "int", "name": "return_int" } ]
      }
    }
    

    What I get after parsing and printing the parsed data to screen:

    {"struct":{"func":[{"name":"return_int","type":"int"}],"var":{"name":"x","type":"int"}}}
    

    Is there a way to get the output exactly in the same order as I defined? So far I'm iterating over members() and entries() and I can't see any other way to do it properly.

    Thanks!

    enhancement 
    Reply
  • It's possible to remove the `iterators` module entirely.
    It's possible to remove the `iterators` module entirely.

    Jul 10, 2016

    We can define type aliases for Members , MembersMut, Entries, EntriesMut, for example:

    pub type Members<'a> = slice::Iter<'a, JsonValue>;
    

    And change methods to return Option:

    pub fn members(&self) -> Option<Members>
    

    It's a little inconvenient on a call site, but that way we can eliminate one level of indirection. @maciejhirsz, If you're ok with this I can implement it.

    discussion enhancement 
    Reply
  • Performance comparison with other existing libraries in README.md
    Performance comparison with other existing libraries in README.md

    Jun 18, 2016

    This would be interesting for many developers and a strong argument to use this library, respectively rust instead of another language.

    In the comparison the version number of the respective lib should be indicated. Especially a comparison with serd is interesting. https://github.com/serde-rs/serde

    documentation enhancement 
    Reply