Skip to content
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

Added convenience methods to the Json class #12780

Merged
merged 1 commit into from
Mar 11, 2014
Merged

Conversation

zslayton
Copy link
Contributor

@zslayton zslayton commented Mar 9, 2014

This is my first non-docs contribution to Rust, so please let me know what I can fix. I probably should've submitted this to the mailing list first for comments, but it didn't take too long to implement so I figured I'd just give it a shot.

These changes are modeled loosely on the JsonNode API provided by the Jackson JSON processor.

Many common use cases for parsing JSON involve pulling one or more fields out of an object, however deeply nested. At present, this requires writing a pyramid of match statements. The added methods in this PR aim to make this a more painless process.

Edited to reflect final implementation

Example JSON:

{
    "successful" : true,
    "status" : 200,
    "error" : null,
    "content" : {
        "vehicles" : [
            {"make" : "Toyota", "model" : "Camry", "year" : 1997},
            {"make" : "Honda", "model" : "Accord", "year" : 2003}
        ]
    }
}

Accessing "successful":

 let example_json : Json = from_str("...above json...").unwrap();
 let was_successful: Option<bool> = example_json.find(&~"successful").and_then(|j| j.as_boolean());

Accessing "status":

 let example_json : Json = from_str("...above json...").unwrap();
 let status_code : Option<f64> = example_json.find(&~"status").and_then(|j| j.as_number());

Accessing "vehicles":

 let example_json : Json = from_str("...above json...").unwrap();
 let vehicle_list: Option<List> = example_json.search(&~"vehicles").and_then(|j| j.as_list());

Accessing "vehicles" with an explicit path:

 let example_json : Json = from_str("...above json...").unwrap();
 let vehicle_list: Option<List> = example_json.find_path(&[&~"content", &~"vehicles"]).and_then(|j| j.as_list());

Accessing "error", which might be null or a string:

 let example_json : Json = from_str("...above json...").unwrap();
 let error: Option<Json> = example_json.find(&~"error");
 if error.is_null() { // This would be nicer as a match, I'm just illustrating the boolean test methods
    println!("Error is null, everything's fine.");
 } else if error.is_str(){
    println!("Something went wrong: {}", error.as_string().unwrap());
}

Some notes:

  • Macros would help to eliminate some of the repetitiveness of the implementation, but I couldn't use them due to Macros should be able to expand to methods #4621. (Edit: There is no longer repetitive impl. Methods were simplified to make them more composable.)
  • Would it be better to name methods after the Json enum type (e.g. get_string) or the associated Rust built-in? (e.g. get_str)
  • TreeMap requires its keys to be &~str. Because of this, all of the new methods required &~str for their parameters. I'm uncertain what the best approach to fixing this is: neither demanding an owned pointer nor allocating within the methods to appease TreeMap's find() seems desirable. If I were able to take &str, people could put together paths easily with "foo.bar.baz".split('.').collect(); (Edit: Follow on investigation into making TreeMap able to search by Equiv would be worthwhile.)
  • At the moment, the find_<sometype> methods all find the first match for the provided key and attempt to return that value if it's of the specified type. This makes sense to me, but it's possible that users would interpret a call to find_boolean("successful") as looking for the first "successful" item that was a boolean rather than looking for the first "successful" and returning None if it isn't boolean. (Edit: No longer relevant.)

I hope this is helpful. Any feedback is appreciated!

@adrientetar
Copy link
Contributor

Travis says you have styling errors (you can check that locally by running make tidy):

/home/travis/build/mozilla/rust/src/libserialize/json.rs:730: trailing whitespace
/home/travis/build/mozilla/rust/src/libserialize/json.rs:767: trailing whitespace
/home/travis/build/mozilla/rust/src/libserialize/json.rs:776: trailing whitespace
/home/travis/build/mozilla/rust/src/libserialize/json.rs:786: trailing whitespace
/home/travis/build/mozilla/rust/src/libserialize/json.rs:792: trailing whitespace
/home/travis/build/mozilla/rust/src/libserialize/json.rs:792: line longer than 100 chars
/home/travis/build/mozilla/rust/src/libserialize/json.rs:799: trailing whitespace
/home/travis/build/mozilla/rust/src/libserialize/json.rs:802: trailing whitespace
/home/travis/build/mozilla/rust/src/libserialize/json.rs:807: trailing whitespace
/home/travis/build/mozilla/rust/src/libserialize/json.rs:810: trailing whitespace
/home/travis/build/mozilla/rust/src/libserialize/json.rs:814: trailing whitespace
/home/travis/build/mozilla/rust/src/libserialize/json.rs:850: trailing whitespace
/home/travis/build/mozilla/rust/src/libserialize/json.rs:898: trailing whitespace
/home/travis/build/mozilla/rust/src/libserialize/json.rs:946: trailing whitespace
/home/travis/build/mozilla/rust/src/libserialize/json.rs:994: trailing whitespace
/home/travis/build/mozilla/rust/src/libserialize/json.rs:1021: trailing whitespace
/home/travis/build/mozilla/rust/src/libserialize/json.rs:1042: trailing whitespace

@zslayton
Copy link
Contributor Author

zslayton commented Mar 9, 2014

Fixed! Thanks, I didn't know about make tidy.

@zslayton
Copy link
Contributor Author

zslayton commented Mar 9, 2014

@alexcrichton Good idea. I'll do the same thing with List while I'm at it.

@zslayton
Copy link
Contributor Author

@alexcrichton I've addressed your notes from yesterday.


/// If the Json value is a List, returns the associated vector.
/// Returns None otherwise.
//pub fn as_list<'a>(&'a self) -> Option<&'a ~[Json]> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line should get deleted

@alexcrichton
Copy link
Member

This looks good to me, thanks! Could you also squash the commits into one?

@zslayton
Copy link
Contributor Author

@alexcrichton Done!

Fixed some styling issues with trailing whitespace.

- Removed redundant functions.
- Renamed `get` to `find`
- Renamed `get_path` to `find_path`
- Renamed `find` to `search`
- Changed as_object and as_list to return Object and List
  rather than the underlying implementation types
  of TreeMap<~str,Json> and ~[Json]
- Refactored find_path to use a fold() instead of recursion

Formatting fixes.

Fixed spacing, deleted comment.

Added convenience methods and accompanying tests to the Json class.

Updated tests to expect less pointer indirection.
@zslayton
Copy link
Contributor Author

/facepalm

I forgot to update the tests to expect less pointer indirection after changing the method signatures.

Fixed, make-check'ed, make-tidy'ed, committed, squashed, rebased, pushed. I'll get the hang of this yet!

bors added a commit that referenced this pull request Mar 11, 2014
This is my first non-docs contribution to Rust, so please let me know what I can fix. I probably should've submitted this to the mailing list first for comments, but it didn't take too long to implement so I figured I'd just give it a shot.

These changes are modeled loosely on the [JsonNode API](http://jackson.codehaus.org/1.7.9/javadoc/org/codehaus/jackson/JsonNode.html) provided by the [Jackson JSON processor](http://jackson.codehaus.org/).

Many common use cases for parsing JSON involve pulling one or more fields out of an object, however deeply nested. At present, this requires writing a pyramid of match statements. The added methods in this PR aim to make this a more painless process.

**Edited to reflect final implementation**

Example JSON:
```json
{
    "successful" : true,
    "status" : 200,
    "error" : null,
    "content" : {
        "vehicles" : [
            {"make" : "Toyota", "model" : "Camry", "year" : 1997},
            {"make" : "Honda", "model" : "Accord", "year" : 2003}
        ]
    }
}
```

Accessing "successful":
```rust
 let example_json : Json = from_str("...above json...").unwrap();
 let was_successful: Option<bool> = example_json.find(&~"successful").and_then(|j| j.as_boolean());
```

Accessing "status":
```rust
 let example_json : Json = from_str("...above json...").unwrap();
 let status_code : Option<f64> = example_json.find(&~"status").and_then(|j| j.as_number());
```

Accessing "vehicles":
```rust
 let example_json : Json = from_str("...above json...").unwrap();
 let vehicle_list: Option<List> = example_json.search(&~"vehicles").and_then(|j| j.as_list());
```

Accessing "vehicles" with an explicit path:
```rust
 let example_json : Json = from_str("...above json...").unwrap();
 let vehicle_list: Option<List> = example_json.find_path(&[&~"content", &~"vehicles"]).and_then(|j| j.as_list());
```

Accessing "error", which might be null or a string:
```rust
 let example_json : Json = from_str("...above json...").unwrap();
 let error: Option<Json> = example_json.find(&~"error");
 if error.is_null() { // This would be nicer as a match, I'm just illustrating the boolean test methods
    println!("Error is null, everything's fine.");
 } else if error.is_str(){
    println!("Something went wrong: {}", error.as_string().unwrap());
}
```

Some notes:
* Macros would help to eliminate some of the repetitiveness of the implementation, but I couldn't use them due to #4621. (**Edit**: There is no longer repetitive impl. Methods were simplified to make them more composable.)
* Would it be better to name methods after the Json enum type (e.g. `get_string`) or the associated Rust built-in? (e.g. `get_str`)
* TreeMap requires its keys to be &~str. Because of this, all of the new methods required &~str for their parameters. I'm uncertain what the best approach to fixing this is: neither demanding an owned pointer nor allocating within the methods to appease TreeMap's find() seems desirable. If I were able to take &str, people could put together paths easily with `"foo.bar.baz".split('.').collect();` (**Edit**: Follow on investigation into making TreeMap able to search by Equiv would be worthwhile.)
* At the moment, the `find_<sometype>` methods all find the first match for the provided key and attempt to return that value if it's of the specified type. This makes sense to me, but it's possible that users would interpret a call to `find_boolean("successful")` as looking for the first "successful" item that was a boolean rather than looking for the first "successful" and returning None if it isn't boolean. (**Edit**: No longer relevant.)

I hope this is helpful. Any feedback is appreciated!
@bors bors closed this Mar 11, 2014
@bors bors merged commit 9e0cfa2 into rust-lang:master Mar 11, 2014
@arjantop
Copy link
Contributor

is_* methods could be implemented in terms of as_* methods (less duplication):

fn is_bool(&self) -> bool {
    self.as_bool().is_some()
}

@zslayton
Copy link
Contributor Author

That's a good idea. I've thought of other a couple of cleanup ideas for these methods since submitting. I've opened #12829 to track this.

flip1995 pushed a commit to flip1995/rust that referenced this pull request May 17, 2024
…ns, r=Manishearth

Ignore `_to_string` lints in code `from_expansion`

Includes the `string_to_string` and `str_to_string` lints.

changelog: [`str_to_string`]: Ignore code from expansion
changelog: [`string_to_string`]: Ignore code from expansion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants