Trying to use decode from rescript-json-combinators library, it is mentionned that Decoders have been made abstract and they have to be run via Decode.decode
let targetValue: JsonCombinators.Json.Decode.decode<Web.Json.t, string>
Trying to use this way raises this error:
This type constructor, JsonCombinators.Json.Decode.decode, can't be found.
Is this the correct way to use it? if not any idea on how this error can be fixed?
I'm not entirely sure what you are trying to do with targetValue here, but the type constructor for a decoder is Json.Decode.t, and only takes one argument, the type it will produce. Then there's a function Json.Decode.decode, which is also available through Json.decode, that takes a decoder and a Js.Json.t value, and runs the decoder on that value to produce a result that, if successful, will contain the decoded value.
Here's an abbreviated version of the README example with some type annotations to hopefully illustrate it better:
type point = {
x: int,
y: int,
}
module Decode = {
open Json.Decode
let point: t<point> = object(field => {
x: field.required(. "x", int),
y: field.required(. "y", int),
})
}
let result: result<point, string> =
Json.decode(json, Decode.point)
switch result {
| Ok(point) =>
let {x, y} = point
Js.log(`Got point - x: ${x}, y: ยข{y}`)
| Error(err) => Js.log(err)
}
Related
I want to initialize a Rust variable with a literal.
Shepmaster guesses that Rust has no map literal because:
the fact that there are multiple data structures that act maplike
(such as both BTreeMap and HashMap) would make it hard to pick one.
But Rust says "you should probably just use Vec or HashMap", meaning that if they are good enough for most purposes, then using them when initializing from a literal value should usually work just fine.
The example map initialization is clumsy, but printing it produces a more concise representation:
{"Mars": 1.5, "Mercury": 0.4, "Earth": 1.0, "Venus": 0.7}.
fn main() {
use std::collections::HashMap;
let solar_distance = HashMap::from([
("Mercury", 0.4),
("Venus", 0.7),
("Earth", 1.0),
("Mars", 1.5),
]);
println!("{:?}", solar_distance)
}
In Python x = {"Mars": 1.5, "Mercury": 0.4, "Earth": 1.0, "Venus": 0.7} would initialize a dict variable. It would be nice if a future version of Rust would accept nested list and map literals to initialize the corresponding default Vec and HashMap structures.
Is a concise nested literal format on the Rust feature roadmap?
A second-best solution would initialize structures from a JSON string (perhaps with some syntactic sugar to avoid quote-escape hell):
fn main() {
use std::collections::HashMap;
let x = String::from("{'Venus': 0.7, 'Mars': 1.5, 'Mercury': 0.4, 'Earth': 1.0}");
println!("{:?}", x);
let solar_distance = HashMap::from(x);
println!("{:?}", solar_distance);
}
But this fails with:
|
7 | let solar_distance = HashMap::from(x);
| ^^^^^^^^^^^^^ the trait `From<String>` is not implemented for `HashMap<_, _, _>`
Has anyone written a method to initialize a nested vec/hash variable from a JSON string?
If you want to be able to initialize a HashMap concisely (similar to vec![]), take a look at https://crates.io/crates/hmap. It does basically exactly what you're describing.
If you want to work with serde_json, you can use the json!() macro provided by that library:
let x = json!({
"foo": "bar",
"baz": [1, 2, 3],
});
I'm using serde to deserialize a JSON file and one of it's values is a String.
To read it, I'm using:
#[serde(default)]
pub key: Option<String>,
because in the JSON file I can have null (then Option handles) or not even pass it (#serde(default)] handles it).
The problem I'm having is that when in the null case, the Option is returning None, which is giving me trouble later. I have to later match the Strings and convert to an i8 like this:
let mut transformed: i8 = 0;
if key.as_ref().unwrap() == "H" {
transformed = 1;
}
else {
transformed = -1; // Case that I'm looking for when null in JSON
}
I searched for match practices to handle the None, but it's also giving me trouble with the String vs &str problem, so I'm looking for a way of when deserializing, assign an empty String "" instead of None, so later I can compare in the same way I'm already doing.
Also would appreciate less verbose solution to directly parse and assign an i8.
As mentioned by mcarton in the comments, the easiest solution is to stick with using an Option<String> in you struct and use .unwrap_or_default() in the code consuming the data. If this isn't an option for you, you can provide a custom deserializer using the #[serde(deserialize_with=...)] attribute:
use serde_json::from_str;
use serde::{Deserialize, Deserializer, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct A {
#[serde(deserialize_with = "null_to_default")]
key: String,
}
fn null_to_default<'de, D, T>(de: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: Default + Deserialize<'de>,
{
let key = Option::<T>::deserialize(de)?;
Ok(key.unwrap_or_default())
}
fn main() {
let a: A = from_str(r#"{"key": null}"#).unwrap();
dbg!(a);
}
(Playground)
I want to convert 3rd party library enums to and from JSON. As I don't want to edit the 3rd party source code I don't want to use the derive macros.
I want to handwrite the serde_json deserialize method. I am thinking that pattern matching is the way to go but the things I need to match on are not public:
extern crate serde_json;
#[test]
fn test_parse_from_json() {
let s = r#"{
"e": 0,
"c": 1,
"n": 2
}"#;
let v: Map<String, Value> = serde_json::from_str(&s).unwrap();
match (&v["e"], &v["c"], &v["n"]) {
(&Value::Number(ref e), &Value::Number(ref c), &Value::Number(ref n)) => {
// e is a PosInt(u64) but that isn't public to match one nor access its methods!
let value = e.n; // error[E0616]: field `n` of struct `serde_json::Number` is private
}
_ => (),
}
}
That doesn't compile. If I replace that inner bit with something I can set a breakpoint on, I can see in the debugger that e is a Number which contains a PosInt(0).
You cannot pattern match on private fields because they are private. You have to use the accessors the library decides to provide. The serde_json documentation for Number shows that it has methods like as_u64:
let value = e.as_u64().expect("not a u64");
As I don't want to edit the 3rd party source code I don't want to use the derive macros.
You may be suffering from the X-Y problem. For example, the Serde docs describe how to implement the traits for a "remote type".
You could also create your own type that you deserialize into and construct a transformation to and from the library type:
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
#[derive(Deserialize)]
struct Mine {
e: u64,
c: u64,
n: u64,
}
#[test]
fn test_parse_from_json() {
let s = r#"{
"e": 0,
"c": 1,
"n": 2
}"#;
let v: Mine = serde_json::from_str(&s).unwrap();
println!("{:?}", (v.e, v.c, v.n));
}
Based on #Shepmaster's answer I got a lot further along:
let v: Map<String, Value> = serde_json::from_str(&s).unwrap();
let bn: Option<BallotNumber>;
match (&v["e"],&v["c"],&v["n"]) {
(&Value::Number(ref e),&Value::Number(ref c),&Value::Number(ref n)) => {
match ( &e.as_u64(), &c.as_u64(), &n.as_u64() ) {
(&Some(era), &Some(count), &Some(number)) =>
bn = Some(BallotNumber::new(era, count, number)),
_ =>
bn = None
}
},
_ =>
bn = None
}
That does the trick only it looks like a bit of a train smash. Given that #Shepmaster's answer points out that serde provided a way around the Orphan rule I will use that approach.
I am using serde_json to deserialise a json document. I have a function that given a string (this is the json document), will return a serde_json Value (this is an enum that represents the json type), returns an Option.
This value is passed around to other functions as required.
However, I realised that passing around a Value is not quite what I want, because doing this, the key is not available.
To illustrate my point, if I have a json document that looks like this:
{
"root" : {
"regex" : null,
"prefixes" : [ "a_", "b_" ]
}
}
"root" is a json object, "regex" is json Null and "prefixes" is a json array.
Now, the json type Value is an enum with discriminators representing the json types, eg Object, Null, Array for the examples given above.
The serde_json crate uses std::collections::BTreeMap to represent nodes in the json document, where the String type repesents the json keys (in the above, these would be "root", "regex" and "prefixes". So passing around just references to Values is only partly helpful, I should be passing around BTreeMap instead, so that I can access the key too.
So this is the following function that I am trying to re-write:
fn get_json_content(content_s : &str) -> Option<Value> {
// instead of returning a value, we need to return a BTreeMap, so we can get the
// key and the value.
println!("===>>> json_content obtained: {}", content_s);
match serde_json::from_str(content_s) { // -> Result<Value>
Ok(some_value) => Some(some_value),
Err(_) => None
}
}
So I started to re-write the function but became up against the "the type of this value must be known in this context" error:
fn get_json_content_as_btreemap<'a>(content_s : &str) -> Option<&'a BTreeMap<String, Value>> {
match serde_json::from_str(content_s) { // -> Result<Value>
Ok(some) => {
// I expect the type of key_value_pair to be BTreeMap<String, Value>>
// (but I may be wrong!)
let key_value_pair = some.as_object().unwrap(); // Error here
},
Err(_) => None
}
}
I found other questions on stackoverflow like this one:
the type of this value must be known in this context
and using this as a helper, I tried to insert the type as follows:
let key_value_pair = some.as_object::<BTreeMap<_, _>>().unwrap();
which doesnt fix the issue. Also, tried other similar variations to no avail. So how do I fix this please?
EDIT:
I have another function in this app as follows:
fn get_root_value<'a>(json_documemt : &'a Value) -> Result<&'a Value, JsonErrorCode> {
if json_documemt.is_object() {
for (k, v) in json_documemt.as_object().unwrap().iter() {
if k == "root" {
println!("found root: {}", k);
return Ok(v)
}
}
return Err(JsonErrorCode::Custom("Failed to find root node".to_string()))
}
Err(JsonErrorCode::Custom("Not an object".to_string()))
}
... and this works fine. Here you can see that I can call as_object() and then obtain the key and value as a tuple pair. I don't understand why as_object is working in one case but not the other. I would like to pull out the BTreeMap and pass this around as a borrowed item.
You can change the return type of your initial function and serde_json will deserialize to the appropriate object if it can:
fn get_json_content(content_s : &str) -> Option<BTreeMap<String, Value>> {
// instead of returning a value, we need to return a BTreeMap, so we can get the
// key and the value.
println!("===>>> json_content obtained: {}", content_s);
match serde_json::from_str(content_s) { // -> Result<Value>
Ok(some_value) => Some(some_value),
Err(_) => None
}
// Note: this match statement can be rewritten as
// serde_json::from_str(content_s).ok()
}
Your second example won't work because you are instantiating the Value object inside the function, and then trying to return a reference to the object you just instantiated. This won't work because the object will go out of scope at the end of the function and the reference will then be invalid.
I am relying on rustc_serialize to parse JSON data into a struct, Orders, which represents a Vec of Order structs. The JSON data may have an array or a null value; my intent is to either parse the array of orders normally, if any, or parse the null value as an Orders with an empty Vec. If neither of these is the case, then an error is to be relayed. This is my attempt:
impl Decodable for Orders {
fn decode<D: Decoder>(d: &mut D) -> Result<Self, D::Error> {
let try = d.read_seq(|d, l| {
let mut orders = Vec::new();
for _ in 0..l {
let order = try!(Decodable::decode(d));
orders.push(order);
}
Ok(Orders(orders))
});
match try {
value # Ok(_) => value,
error # Err(e) => match e {
ExpectedError(_, x) if &x == "null" => Ok(Orders(Vec::new())),
_ => error,
},
}
}
}
My issue has to do with pattern matching on ExpectedError. The compiler gives me the following error:
expected `<D as rustc_serialize::serialize::Decoder>::Error`,
found `rustc_serialize::json::DecoderError`
(expected associated type,
found enum `rustc_serialize::json::DecoderError`) [E0308]
src/api/types/json.rs:125 ExpectedError(_, x) if &x == "null" => Ok(Orders(Vec::new())),
^~~~~~~~~~~~~~~~~~~
I am stumped on this one. How can I correct this?
How can I correct this?
In general, you would have to choose between being generic or specialized. You cannot pattern match on an associated type because this type can be anything and a generic method should work for any type which satisfies the constraints.
For example, in your case:
<D as rustc_serialize::serialize::Decoder>::Error can be anything
rustc_serialize::json::DecoderError is but one possibility
So you should normally choose between using some abstract D or specializing the decoding for json.
However, since you are implementing Decodable here, you cannot choose NOT to be generic as you do not get to pick the signature.
Furthermore, it appears that rustc_serialize does not support tentative parsing (or at least, its json implementation does not), so I am afraid that you cannot check for nullity (using d.read_nil()) beforehand.
I suppose those limitations can be seen as the reason that this library is being retired in favor of serde, and can only encourage you to try it out.