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],
});
Related
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)
}
I need to check if all items are unique in an array of serde_json::Value. Since this type does not implement Hash I came up with the following solution:
use serde_json::{json, Value};
use std::collections::HashSet;
fn is_unique(items: &[Value]) -> bool {
let mut seen = HashSet::with_capacity(items.len());
for item in items.iter() {
if !seen.insert(item.to_string()) {
return false;
}
}
true
}
fn main() {
let value1 = json!([1, 2]);
assert!(is_unique(&value1.as_array().unwrap()));
let value2 = json!([1, 1]);
assert!(!is_unique(&value2.as_array().unwrap()));
}
I assume that it should only work if serde_json is built with preserve_order feature (to have objects serialized in the same order every time), but I am not 100% sure about it.
Main usage context:
JSON Schema validation. "uniqueItems" keyword implementation.
Related usage case
Deduplication of JSON arrays to optimize JSON Schema inference on them.
For example, the input data is [1, 2, {"foo": "bar"}]. A straightforward inference might output this:
{
"type": "array",
"items": {
"anyOf": [
{"type": "integer"},
{"type": "integer"},
{"type": "object", "required": ["foo"]}
]
}
}
values in items/anyOf can be reduced to only two values.
Question: What would be the most time-efficient and correct way to check that there are no duplicates in an arbitrary JSON array?
I used serde_json = "1.0.48"
Rust: 1.42.0
Playground
Converting each array item to a string is rather expensive – it requires at least one string allocation per item, and quite likely more than that. It's also difficult to make sure mappings (or "objects" in JSON language) are represented in a canonical form.
A faster and more robust alternative is to implement Hash for Value yourself. You need to define a newtype wrapper, since you can't implement a foreign trait on a foreign type. Here's a simple example implementation:
use serde_json::Value;
use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;
#[derive(PartialEq)]
struct HashValue<'a>(pub &'a Value);
impl Eq for HashValue<'_> {}
impl Hash for HashValue<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
use Value::*;
match self.0 {
Null => state.write_u32(3_221_225_473), // chosen randomly
Bool(ref b) => b.hash(state),
Number(ref n) => {
if let Some(x) = n.as_u64() {
x.hash(state);
} else if let Some(x) = n.as_i64() {
x.hash(state);
} else if let Some(x) = n.as_f64() {
// `f64` does not implement `Hash`. However, floats in JSON are guaranteed to be
// finite, so we can use the `Hash` implementation in the `ordered-float` crate.
ordered_float::NotNan::new(x).unwrap().hash(state);
}
}
String(ref s) => s.hash(state),
Array(ref v) => {
for x in v {
HashValue(x).hash(state);
}
}
Object(ref map) => {
let mut hash = 0;
for (k, v) in map {
// We have no way of building a new hasher of type `H`, so we
// hardcode using the default hasher of a hash map.
let mut item_hasher = DefaultHasher::new();
k.hash(&mut item_hasher);
HashValue(v).hash(&mut item_hasher);
hash ^= item_hasher.finish();
}
state.write_u64(hash);
}
}
}
}
The value for None is chosen randomly to make it unlikely to collide with other entries. To calculate hashes for floating point numbers, I used the ordered-float crate. For mappings, the code calculates a hash for each key/value pair and simply XORs these hashes together, which is order-independent. It's a bit unfortunate that we need to hardcode the hasher used for hashing the map entries. We could abstract that out by defining our own version of the Hash trait, and then derive concrete implementations of std::hash::Hash from our custom Hash trait, but this complicates the code quite a bit, so I wouldn't do that unless you need to.
We can't derive Eq, since Value does not implement Eq. However, I believe this is just an oversight, so I filed an issue to add an Eq implementation (which the PR has been accepted for, so it will land in some future release).
Depends if JSON array is sorted or not. If it is sorted you can use binary search to check the value is matched with other values. To sort you can use merge sort. Total complexity will be O(nlogn + logn). Or you can iterate sequentially and check for duplicate rows O(n^2).
I'd like to read multiple JSON objects from a file/reader in Rust, one at a time. Unfortunately serde_json::from_reader(...) just reads until end-of-file; there doesn't seem to be any way to use it to read a single object or to lazily iterate over the objects.
Is there any way to do this? Using serde_json would be ideal, but if there's a different library I'd be willing use that instead.
At the moment I'm putting each object on a separate line and parsing them individually, but I would really prefer not to need to do this.
Example Use
main.rs
use serde_json;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let stdin = std::io::stdin();
let stdin = stdin.lock();
for item in serde_json::iter_from_reader(stdin) {
println!("Got {:?}", item);
}
Ok(())
}
in.txt
{"foo": ["bar", "baz"]} 1 2 [] 4 5 6
example session
Got Object({"foo": Array([String("bar"), String("baz")])})
Got Number(1)
Got Number(2)
Got Array([])
Got Number(4)
Got Number(5)
Got Number(6)
This was a pain when I wanted to do it in Python, but fortunately in Rust this is a directly-supported feature of the de-facto-standard serde_json crate! It isn't exposed as a single convenience function, but we just need to create a serde_json::Deserializer reading from our file/reader, then use its .into_iter() method to get a StreamDeserializer iterator yielding Results containing serde_json::Value JSON values.
use serde_json; // 1.0.39
fn main() -> Result<(), Box<dyn std::error::Error>> {
let stdin = std::io::stdin();
let stdin = stdin.lock();
let deserializer = serde_json::Deserializer::from_reader(stdin);
let iterator = deserializer.into_iter::<serde_json::Value>();
for item in iterator {
println!("Got {:?}", item?);
}
Ok(())
}
One thing to be aware of: if a syntax error is encountered, the iterator will start to produce an infinite sequence of error results and never move on. You need to make sure you handle the errors inside of the loop, or the loop will never end. In the snippet above, we do this by using the ? question mark operator to break the loop and return the first serde_json::Result::Err from our function.
During my fresh adventures with kotlin-react I hit a hard stop when trying to parse some data from my backend which contains enum values.
Spring-Boot sends the object in JSON form like this:
{
"id": 1,
"username": "Johnny",
"role": "CLIENT"
}
role in this case is the enum value and can have the two values CLIENT and LECTURER. If I were to parse this with a java library or let this be handled by Spring-Boot, role would be parsed to the corresponding enum value.
With kotlin-js' JSON.parse, that wouldn't work and I would have a simple string value in there.
After some testing, I came up with this snippet
val json = """{
"id": 1,
"username": "Johnny",
"role": "CLIENT",
}"""
val member: Member = JSON.parse(json) { key: String, value: Any? ->
if (key == "role") Member.Role.valueOf(value.toString())
else value
}
in which I manually have to define the conversion from the string value to the enum.
Is there something I am missing that would simplify this behaviour?
(I am not referring to using ids for the JSON and the looking those up, etc. I am curious about some method in Kotlin-JS)
I have the assumption there is not because the "original" JSON.parse in JS doesn't do this and Kotlin does not add any additional stuff in there but I still have hope!
As far as I know, no.
The problem
Kotlin.JS produces an incredibly weird type situation when deserializing using the embedded JSON class, which actually is a mirror for JavaScript's JSON class. While I haven't done much JavaScript, its type handling is near non-existent. Only manual throws can enforce it, so JSON.parse doesn't care if it returns a SomeCustomObject or a newly created object with the exact same fields.
As an example of that, if you have two different classes with the same field names (no inheritance), and have a function that accepts a variable, it doesn't care which of those (or a third for that matter) it receives as long as the variables it tries accessing on the class exists.
The type issues manifest themselves into Kotlin. Now wrapping it back to Kotlin, consider this code:
val json = """{
"x": 1, "y": "yes", "z": {
"x": 42, "y": 314159, "z": 444
}
}""".trimIndent()
data class SomeClass(val x: Int, val y: String, val z: Struct)
data class Struct(val x: Int, val y: Int, val z: Int)
fun main(args: Array<String>) {
val someInstance = JSON.parse<SomeClass>(json)
if(someInstance.z::class != Struct::class) {
println("Incompatible types: Required ${Struct::class}, found ${someInstance.z::class}");
}
}
What would you expect this to print? The natural would be to expect a Struct. The type is also explicitly declared
Unfortunately, that is not the case. Instead, it prints:
Incompatible types: Required class Struct, found class Any
The point
The embedded JSON de/serializer isn't good with types. You might be able to fix this by using a different serializing library, but I'll avoid turning this into a "use [this] library".
Essentially, JSON.parse fails to parse objects as expected. If you entirely remove the arguments and try a raw JSON.parse(json); on the JSON in your question, you'll get a role that is a String and not a Role, which you might expect. And with JSON.parse doing no type conversion what so ever, that means you have two options: using a library, or using your approach.
Your approach will unfortunately get complicated if you have nested objects, but with the types being changed, the only option you appear to have left is explicitly parsing the objects manually.
TL;DR: your approach is fine.
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.