I ran through the standard JSON library of Rust http://doc.rust-lang.org/serialize/json/ and couldn't figure out what represents a node in it. In Java it's JsonNode. What's it in Rust? For example, how can I pass an argument of the type JsonNode in Rust?
Rust's "DOM" for JSON is defined by Json enum. For example, this JSON object:
{ "array": [1, 2, 3], "submap": { "bool": true, "string": "abcde" } }
is represented by this expression in Rust:
macro_rules! tree_map {
($($k:expr -> $v:expr),*) => ({
let mut r = ::std::collections::TreeMap::new();
$(r.insert($k, $v);)*
r
})
}
let data = json::Object(tree_map! {
"array".to_string() -> json::List(vec![json::U64(1), json::U64(2), json::U64(3)]),
"submap".to_string() -> json::Object(tree_map! {
"bool".to_string() -> json::Boolean(true),
"string".to_string() -> json::String("abcde".to_string())
})
});
(try it here)
I've used custom map construction macro because unfortunately Rust standard library does not provide one (yet, I hope).
Json is just a regular enum, so you have to use pattern matching to extract values from it. Object contains an instance of TreeMap, so then you have to use its methods to inspect object structure:
if let json::Object(ref m) = data {
if let Some(value) = m.find_with(|k| "submap".cmp(k)) {
println!("Found value at 'submap' key: {}", value);
} else {
println!("'submap' key does not exist");
}
} else {
println!("data is not an object")
}
Update
Apparently, Json provides a lot of convenience methods, including find(), which will return Option<&Json> if the target is an Object which has corresponding key:
if let Some(value) = data.find("submap") {
println!("Found value at 'submap' key: {}", value);
} else {
println!("'submap' key does not exist or data is not an Object");
}
Thanks #ChrisMorgan for the finding.
Related
I wrote a function that takes as input a structure (of type Struct) that can contain primitive types as well as Map and Set objects, and converts it into something that can be JSON-serialized. Examples of input:
let a = 'hello'; // -> "hello"
let b = new Map<string, Set<number>>([['a', new Set<number>([1, 3])]]); // -> {"a": [1, 3]}
let c = {a: new Set<number[]>([[1, 2]])}; // -> {"a": [[1, 2]]}
let d = [{e: false}]; // -> [{"e": false}]
However, I find my code particularly verbose, and I am really not sure about its safety:
type Json = string | number | boolean | null | Json[] | { [key: string]: Json };
type Struct = Json | Struct[] | { [key: string]: Struct } | Map<string, Struct> | Set<Struct>;
function isJson(test: any): test is Json {
if (test == null || ['string', 'number', 'boolean'].indexOf(typeof test) != -1)
return true;
if (Array.isArray(test)) {
// if at least one of the values is not JSON serializable, the array is not JSON-serializable
for (let value of test)
if (!isJson(value))
return false;
return true;
}
if (typeof test == 'object') {
// if it is not a plain object, the object is not JSON-serializable
if (Object.getPrototypeOf(test) != null && test.constructor != Object)
return false;
// if there are symbol properties, the object is not JSON-serializable
if (Object.getOwnPropertySymbols(test).length > 0)
return false;
// if at least one of the values is not JSON serializable, the object is not JSON-serializable
for (let [key, value] of Object.entries(test))
if (!isJson(test[key]))
return false;
return true;
}
return false;
}
function toJson(struct: Struct) {
let json: Json = null;
if (isJson(struct))
json = struct;
else if (Array.isArray(struct) || struct instanceof Set) {
json = [];
let structCast = struct instanceof Set ? struct as Set<Struct> : struct as Struct[];
for (let value of structCast)
json.push(toJson(value));
}
else if (Object.getPrototypeOf(struct) == null || struct.constructor == Object || struct instanceof Map) {
json = {};
let structCast = struct instanceof Map ? struct as Map<string, Struct> : Object.entries(struct);
for (let [key, value] of structCast)
json[key] = toJson(value);
}
return json;
}
I am especially annoyed by the isJson function. Is there no way to get rid of it? I know that most of the typechecking is lost after compilation in TypeScript, but is there a better way do what I want?
Thank you for your help.
I'm going to assume that you're writing TypeScript code and the users of toJson() are also writing TypeScript code. If that's not true, and if the toJson() function is called by some pure JavaScript code somewhere, then you lose any compile time guarantees and will need to add as many runtime checks to toJson() as you feel necessary. From here on out I assume all the relevant code will pass through a compile step.
In this case, you can probably stop worrying about some of the strange edge cases you're dealing with inside isJson(). If the TypeScript Struct type definition already prohibits something then you don't need to write runtime code to deal with it. On the other hand, anything that Struct allows but you'd like to disallow will need runtime checks. For example, it's not generally possible in TypeScript to say "reject object types with any symbol-valued keys". You can use generics to make the compiler reject any object types known to have such keys, but this is more complex, and does not work on the general case where an object of type like {a: string} may or may not have additional unknown keys. I would suggest that, if you really need such checks, to do so by throw statement, since you can't catch them before runtime anyway (unless you want toJson() to return Json | undefined or some other failure marker).
So, let's approach this by merging the isJson() checks we need into the toJson() function and eliminating isJson(). Here's one possible implementation of toJson():
function toJson(struct: Struct): Json {
if (struct === null || typeof struct === "string" ||
typeof struct === "number" || typeof struct === "boolean") {
return struct;
}
if (Array.isArray(struct)) {
return struct.map(toJson);
}
if (struct instanceof Set) {
return Array.from(struct).map(toJson);
}
if (struct instanceof Map) {
return Object.fromEntries(
Array.from(struct).map(([k, v]) => [k, toJson(v)])
);
}
return Object.fromEntries(
Object.entries(struct).map(([k, v]) => [k, toJson(v)])
);
}
The four primitive checks at the beginning could be refactored to an array.includes() or similar code, but then the TypeScript compiler won't understand what's going on and you'd need return struct as Json. The way I've got it is more verbose but the compiler is 100% sure that struct is a valid Json inside that block. Either way is fine.
The rest of the checks are just using built in JavaScript functions and methods like Array.from(), Array.prototype.map(), Object.entries() and Object.fromEntries().
As for the typings, if you want, you can give toJson() a call signature that tries to actually map the input type to the output type. For example:
type ToJson<T> =
Struct extends T ? Json :
Json extends T ? T :
T extends Map<infer K, infer S> ? { [P in Extract<K, string>]: ToJson<S> } :
T extends Set<infer S> ? ToJson<S>[] :
T extends object ? { [K in keyof T]: ToJson<T[K]> } :
T;
function toJson<T extends Struct>(struct: T): ToJson<T>;
ToJson<T> is similar to the implementation, but represented as a type operation. If the input type T is just Struct, then output Json. If it's already a subtype of Json, then output T. If it's a Map<K, S>, then produce an object whose keys are K and whose values are ToJson<S>. If it's a Set<S>, then output an array whose elements are ToJson<S>. And if it's an object (arrays are included), then map all the entries via ToJson. Finally, if it's just a primitive type, return the primitive type.
Let's see if this all works:
const foo = {
str: "hello",
num: 123,
boo: true,
nul: null,
jArr: ["one", 2, { x: 3 }],
jObj: { a: "one", b: 2, c: { x: 3 } },
set: new Set([1, 2, 3]),
map: new Map([["a", 1], ["b", 2]])
}
const json = toJson(foo);
The type seen by the compiler is as follows:
/* const json: {
str: string;
num: number;
boo: boolean;
nul: null;
jArr: (string | number | {
x: number;
})[];
jObj: {
a: string;
b: number;
c: {
x: number;
};
};
set: number[];
map: {
[x: string]: number;
};
} */
Note how the set and map properties have been transformed. Let's make sure the implementation worked also:
console.log(json);
/* {
"str": "hello",
"num": 123,
"boo": true,
"nul": null,
"jArr": [
"one",
2,
{
"x": 3
}
],
"jObj": {
"a": "one",
"b": 2,
"c": {
"x": 3
}
},
"set": [
1,
2,
3
],
"map": {
"a": 1,
"b": 2
}
} */
Also good. The fact that the compiler type and the implementation value agree means that you can, if you want, perform other operations on it and get some type checking and hints:
console.log(json.set[0].toFixed(2)); // 1.00
Playground link to code
I am trying to parse a json file that looks like this:
{
"MyApp": {
"pro1" : {
"enable": true
},
"pro2" : {
"enable": true
}
},
"pro3" : {
"pro4" : true,
"pro5" : true,
"pro6" : true,
"pro7" : true,
"pro8" : true,
"pro10" : true
},
"pro11": {
"pro12": false,
"pro13": false,
"pro14": false
},
"pro15": {
"prob16": true
},
"prob16": {
"prob17": {
"prob18": true,
}
},
"prob19": {
"prob20": {
"prob21": {
"prob22": false
}
}
},
"prob23": true,
"prob24": true
}
I am trying to parse it in a way that provides easy access. I first parsed the json file into a json object with type [String:Any], then I tried to put the pairs into [String:[String:[String:Bool]]] but then I realize a problem is that I don't know how many layers might be there. Maybe there will be pairs within pairs within pairs..
But If do know the layers, say the maximum layer is 4, do I still put this as a map? map within 3 other maps? Is there any better data structure to put this into?
(This is a partial answer, I suspect you will immediately have more questions, but until I know how you're going to use this data structure, I didn't want to write the helpers you'll need.)
As you say, each stage of this is either a boolean value, or another layer mapping strings to more stages. So say that in a type. When you describe something using the word or, that generally tells you it's an enum.
// Each level of Settings is either a value (bool) or more settings.
enum Settings {
// Note that this is not order-preserving; it's possible to fix that if needed
indirect case settings([String: Settings])
case value(Bool)
}
You don't know the keys, so you need "any key," which is something that probably should be in stdlib, but it's easy to write.
// A CodingKey that handle any string
struct AnyStringKey: CodingKey {
var stringValue: String
init?(stringValue: String) { self.stringValue = stringValue }
var intValue: Int?
init?(intValue: Int) { return nil }
}
With those, decoding is just recursively walking the tree, and decoding either a level or a value.
extension Settings: Decodable {
init(from decoder: Decoder) throws {
// Try to treat this as a settings dictionary
if let container = try? decoder.container(keyedBy: AnyStringKey.self) {
// Turn all the keys in to key/settings pairs (recursively).
let keyValues = try container.allKeys.map { key in
(key.stringValue, try container.decode(Settings.self, forKey: key))
}
// Turn those into a dictionary (if dupes, keep the first)
let level = Dictionary(keyValues, uniquingKeysWith: { first, _ in first })
self = .settings(level)
} else {
// Otherwise, it had better be a boolen
self = .value(try decoder.singleValueContainer().decode(Bool.self))
}
}
}
let result = try JSONDecoder().decode(Settings.self, from: json)
(How you access this conveniently depends a bit on what you want that table view to look like; what's in each row, what's your UITableViewDataSource look like? I'm happy to help through that if you'll explain in the question how you want to use this data.)
Swift Runner
The following code is probably way too complicated for you to really use, but I want to explore what kind of interface you're looking for. This data structure is quite complicated, and it's still very unclear to me how you want to consume it. It would help for you to write some code that uses this result, and then I can help write code that matches that calling code.
But one way you can think about this data structure is that it's a "dictionary" that can be indexed by a "path", which is a [String]. So one path is ["prob23"] and one path is ["prob19", "prob20", "prob21", "prob22"].
So to subscript into that, we could do this:
extension Settings {
// This is generic so it can handle both [String] and Slice<[String]>
// Some of this could be simplified by making a SettingsPath type.
subscript<Path>(path: Path) -> Bool?
where Path: Collection, Path.Element == String {
switch self {
case .value(let value):
// If this is a value, and there's no more path, return the value
return path.isEmpty ? value : nil
case .settings(let settings):
// If this is another layer of settings, recurse down one layer
guard let key = path.first else { return nil }
return settings[key]?[path.dropFirst()]
}
}
}
This isn't a real dictionary. It's not even a real Collection. It's just a data structure with subscript syntax. But with this, you can say:
result[["pro3", "pro4"]] // true
And, similarly, you get all the paths.
extension Settings {
var paths: [[String]] {
switch self {
case .settings(let settings):
// For each key, prepend it to all its children's keys until you get to a value
let result: [[[String]]] = settings.map { kv in
let key = kv.key
let value = kv.value
switch value {
case .value:
return [[key]] // Base case
case .settings:
return value.paths.map { [key] + $0 } // Recurse and add our key
}
}
// The result of that is [[[String]]] because we looped twice over something
// that was already an array. We want to flatten it back down one layer to [[String]]
return Array(result.joined())
case .value:
return [] // This isn't the base case; this is just in case you call .paths on a value.
}
}
}
for path in result.paths {
print("\(path): \(result[path]!)")
}
==>
["pro15", "prob16"]: true
["pro3", "pro4"]: true
["pro3", "pro10"]: true
["pro3", "pro7"]: true
["pro3", "pro8"]: true
["pro3", "pro5"]: true
["pro3", "pro6"]: true
["prob19", "prob20", "prob21", "prob22"]: false
["prob23"]: true
["prob24"]: true
["MyApp", "pro1", "enable"]: true
["MyApp", "pro2", "enable"]: true
["prob16", "prob17", "prob18"]: true
["pro11", "pro13"]: false
["pro11", "pro14"]: false
["pro11", "pro12"]: false
I know this is too complex an answer, but it may start getting you into the right way of thinking about the problem and what you want out of this data structure. Figure out your use case, and the rest will flow from that.
In my opinion the best approach is to use AlamoFire, PromiseKit and SwiftyJSON those 3 Swift libraries will make parsing this complex JSON easier and in fewer lines and for sure it will save you lots of time, also they are highly documented.
Some sample code of how parsing with this libraries would work on the MyApp JSON field:
func DownloadDataAndParseJSON() {
let headers = ["headerParameter": someParameter]
Alamofire.request(someUrl, method: .get, headers: headers).responseJSON { response in
let json = JSON(response.result.value as Any)
if let allJsonFields = json.array {
for item in allJsonFields{
let myApp = item["MyApp"].array ?? []
for item in myApp{
let pro1 = item["pro1"].array ?? []
let pro2 = item["pro2"].array ?? []
for item in pro1{
let enable = item["enable"].bool ?? false}
for item in pro2{
let enable = item["enable"].bool ?? false}
}
//Here you can append data to dictionary, object or struct.
}
}
}
}
See how you can turn the parsed value to a boolean with .bool and also you can use the ?? parameter to add an optional default value in case the JSON throws a nil or empty field.
I'm parsing strings into JSON objects, and I need to be able to recursively iterate through the attributes of the objects. So I'm trying to create a function which iterates through the attributes of the object, and if an attribute is not a primitive then call the function again (recursion) with the attribute itself.
In Javascript I'd solve it like this:
function forEachAttribute(object) {
for (let key in object) {
let attribute = object[key];
if (typeof attribute === "object") {
forEachAttribute(attribute);
} else {
console.log(key + ": " + attribute);
}
}
}
let myObject = {
innerObject: {
x: 123
},
y: 456
};
forEachAttribute(myObject);
But I'm moving away from Javascript, and trying to learn how to use Kotlin instead. So I found a way to iterate through the attributes of a JSON object.
But I don't quite understand how to determine if the attribute is a primitive or not.
import kotlin.js.Json
fun iterateThroughAttributes(jsonObject: Json) {
for (key in js("Object").keys(jsonObject)) {
val attribute = jsonObject[key]
// How do I determine if the attribute is a primitive, or not?
}
}
fun main (args: Array<String>) {
val someString = "(some json string)"
val jsonObject = JSON.parse<Json>(someString)
iterateThroughAttributes(jsonObject)
}
Can someone help?
You may check if an object is of some type using is which is equivalent of instanceOf in java.
For details, refer this question.
I am trying to iterate json data in angular2.
If JSON Data is like this
{fileName: "XYZ"}
I am able to iterate using- let data of datas
But If my JSON data key is in string format, how I can iterate in angular2?
{"fileName": "XYZ"}
JSON always have double quoted string keys, so these:
{ fileName: "XYZ" }
{ 'fileName': "XYZ" }
Are not valid jsons, but this is:
{ "fileName": "XYZ" }
Javascript objects don't require the keys to be quoted, and if they are then a single quote can be used:
let a = { fileName: "XYZ" };
let b = { 'fileName': "XYZ" };
let c = { "fileName": "XYZ" };
Here a, b and c are equivalent.
In any case, iterating all of those js object is done in the same way:
for (let key in a) {
console.log(`${ key }: ${ a[key] }`);
}
Object.keys(b).forEach(key => console.log(`${ key }: ${ b[key] }`));
I'm trying to create an iterator trait that provides a specific type of resource, so I can implement multiple source types. I'd like to create a source for reading from a CSV file, a binary etc..
I'm using the rust-csv library for deserializing CSV data:
#[derive(RustcDecodable)]
struct BarRecord {
bar: u32
}
trait BarSource : Iterator {}
struct CSVBarSource {
records: csv::DecodedRecords<'static, std::fs::File, BarRecord>,
}
impl CSVBarSource {
pub fn new(path: String) -> Option<CSVBarSource> {
match csv::Reader::from_file(path) {
Ok(reader) => Some(CSVBarSource { records: reader.decode() }),
Err(_) => None
}
}
}
impl Iterator for CSVBarSource {
type Item = BarRecord;
fn next(&mut self) -> Option<BarRecord> {
match self.records.next() {
Some(Ok(e)) => Some(e),
_ => None
}
}
}
I cannot seem to store a reference to the DecodedRecords iterator returned by the CSV reader due to lifetime issues:
error: reader does not live long enough
How can I store a reference to the decoded records iterator and what am I doing wrong?
According to the documentation, Reader::decode is defined as:
fn decode<'a, D: Decodable>(&'a mut self) -> DecodedRecords<'a, R, D>
That is reader.decode() cannot outlive reader (because of 'a).
And with this declaration:
struct CSVBarSource {
records: csv::DecodedRecords<'static, std::fs::File, BarRecord>,
// ^~~~~~~
}
reader would need a 'static lifetime, that is it would need to live forever, which it does not hence the error you get “reader does not live long enough”.
You should store reader directly in CSVBarSource:
struct CSVBarSource {
reader: csv::Reader<std::fs::File>,
}
And call decode only as needed.