How do I iterate over a JSON list? (rust) - json

I am loafing a gltf asset (basically a big json file). I want to iterate over one of the fields, i.e. I am doing this:
let data = fs::read_to_string("Assets/werewolf_animated.gltf").expect("Unable to read file");
let json: serde_json::Value =
serde_json::from_str(&data).expect("JSON was not well-formatted");
for skin in json["skins"].into()
{
println!("{:?}", skin["inverseBindMatrices"]);
}
But apparently rust cannot infer the type, I get:
println!("{:?}", skin["inverseBindMatrices"]);
| ^^^^ cannot infer type
How can I iterate over every entry in the skins json array?
For what is worth this is what the specific field looks like in the json file:
"skins" : [
{
"inverseBindMatrices" : 5,
"joints" : [
64,
53,
52,
51,
2,
],
"name" : "Armature"
}
],

Value is an enum of all of the possible types of JSON values, so you have to handle the non-array cases as well (null values, booleans, numbers, strings, and objects). The helper Value::as_array() method returns an Option which is None in the non-array cases:
if let Some(skins) = json["skins"].as_array() {
for skin in skins {
// ...
}
}
In this case, this is pretty much equivalent to:
if let serde_json::Value::Array(skins) = json["skins"] {
for skin in skins {
// ...
}
}
If you want to signal an error via Result when this value isn't an array, you can combine Value::as_array() with Option::ok_or(). Here is an example, which assumes the presence of a custom error enum DataParseError:
for skin in json["skins"].as_array().ok_or(DataParseError::SkinsNotAnArray)? {
// ...
}

Related

How to iterate through nested dynamic JSON file in Flutter

So I have two JSON files with different fields.
{
"password": { "length": 5, "reset": true},
}
"dataSettings": {
"enabled": true,
"algorithm": { "djikstra": true },
"country": { "states": {"USA": true, "Romania": false}}
}
I want to be able to use the same code to be able to print out all the nested fields and its values in the JSON.
I tried using a [JSON to Dart converter package](https://javiercbk.github.io/json_to_dart/.
However, it seems like using this would make it so I would have to hardcode all the values, since I would retrieve it by doing
item.dataSettings.country.states.USA
which is a hardcoded method of doing it. Instead I want a way to loop through all the nested values and print it out without having to write it out myself.
You can use ´dart:convert´ to convert the JSON string into an object of type Map<String, dynamic>:
import 'dart:convert';
final jsonData = "{'someKey' : 'someValue'}";
final parsedJson = jsonDecode(jsonData);
You can then iterate over this dict like any other:
parsedJson.forEach((key, value) {
// ... Do something with the key and value
));
For your use case of listing all keys and values, a recursive implementation might be most easy to implement:
void printMapContent(Map<String, dynamic> map) {
parsedJson.forEach((key, value) {
print("Key: $key");
if (value is String) {
print("Value: $value");
} else if (value is Map<String, dynamic>) {
// Recursive call
printMapContent(value);
}
));
}
But be aware that this type of recursive JSON parsing is generally not recommendable, because it is very unstructured and prone to errors. You should know what your data structure coming from the backend looks like and parse this data into well-structured objects.
There you can also perform input validation and verify the data is reasonable.
You can read up on the topic of "JSON parsing in dart", e.g. in this blog article.

How to Import JSON data to typescript Map<K, V> type?

I am trying to import json data that might include present or absent mappings within one of the properties, and figured the correct data type to represent this was Map<string, number>, but I'm getting an error when I try to do this.
My JSON file, data.json, looks like this:
{
"datas": [
{
"name":"test1",
"config":"origin",
"entries": {
"red":1,
"green":2
}
}
,
{
"name":"test2",
"config":"remote",
"entries": {
"red":1,
"blue":3
}
}
,
{
"name":"test3",
"entries": {
"red":1,
"blue":3,
"purple":3
}
}
]
}
My typescript code, Data.ts, which attempts to read it looks like this:
import data from './data.json';
export class Data {
public name:string;
public config:string;
public entries:Map<string, number>;
constructor(
name:string,
entries:Map<string, number>,
config?:string
) {
this.name = name;
this.entries = entries;
this.config = config ?? "origin";
}
}
export class DataManager {
public datas:Data[] = data.datas;
}
But that last line, public datas:Data[] = data.datas;, is throwing an error.
Is there a proper way to import data like this?
The goal, ultimately, is to achieve three things:
Any time entries is present, it should receive some validation that it only contains properties of type number; what those properties are is unknown to the programmer, but will be relevant to the end-user.
If config is absent in the JSON file, the construction of Data objects should supply a default value (here, it's "origin")
This assignment of the data should occur with as little boilerplate code as possible. If, down the line Data is updated to have a new property (and Data.json might or might not receive updates to its data to correspond), I don't want to have to change how DataManager.data receives the values
Is this possible, and if so, what's the correct way to write code that will do this?
The lightest weight approach to this would not be to create or use classes for your data. You can instead use plain JavaScript objects, and just describe their types strongly enough for your use cases. So instead of a Data class, you can have an interface, and instead of using instances of the Map class with string-valued keys, you can just use a plain object with a string index signature to represent the type of data you already have:
interface Data {
name: string;
config: string;
entries: { [k: string]: number }
}
To make a valid Data, you don't need to use new anywhere; just make an object literal with name, config, and entries properties of the right types. The entries property is { [k: string]: number }, which means that you don't know or care what the keys are (other than the fact that they are strings as opposed to symbols), but the property values at those keys should be numbers.
Armed with that definition, let's convert data.datas to Data[] in a way that meets your three criteria:
const datas: Data[] = data.datas.map(d => ({
config: "origin", // any default values you want
...d, // the object
entries: onlyNumberValues(d.entries ?? {}) // filter out non-numeric entries
}));
function onlyNumberValues(x: { [k: string]: unknown }): { [k: string]: number } {
return Object.fromEntries(
Object.entries(x).filter(
(kv): kv is [string, number] => typeof kv[1] === "number"
)
);
}
The above sets the entries property to be a filtered version of the entries property in the incoming data, if it exists. (If entries does not exist, we use an empty object {}). The filter function is onlyNumberValues(), which breaks the object into its entries via the Object.entries() method, filters these entries with a user-defined type guard function, and packages them back into an object via the Object.fromEntries() method. The details of this function's implementation can be changed, but the idea is that you perform whatever validation/transformation you need here.
Any required property that may be absent in the JSON file should be given a default value. We do this by creating an object literal that starts with these default properties, after which we spread in the properties from the JSON object. We do this with the config property above. If the JSON object has a config property, it will overwrite the default when spread in. (At the very end we add in the entries property explicitly, to overwrite the value in the object with the filtered version).
Because we've spread the JSON object in, any properties added to the JSON object will automatically be added. Just remember to specify any defaults for these new properties, if they are required.
Let's make sure this works as desired:
console.log(datas)
/* [{
"config": "origin",
"name": "test1",
"entries": {
"red": 1,
"green": 2
}
}, {
"config": "remote",
"name": "test2",
"entries": {
"red": 1,
"blue": 3
}
}, {
"config": "origin",
"name": "test3",
"entries": {
"red": 1,
"blue": 3,
"purple": 3
}
}] */
Looks good.
Playground link to code

Marshal dynamic JSON field tags in Go

I'm trying to generate JSON for a Terraform file. Because I (think I) want to use marshalling instead of rolling my own JSON, I'm using Terraforms JSON format instead of the 'native' TF format.
{
"resource": [
{
"aws_instance": {
"web1": {
"some": "data"
}
}]
}
resource and aws_instance are static identifiers while web1 in this case is the random name. Also it wouldn't be unthinkable to also have web2 and web3.
type Resource struct {
AwsResource AwsResource `json:"aws_instance,omitempty"`
}
type AwsResource struct {
AwsWebInstance AwsWebInstance `json:"web1,omitempty"`
}
The problem however; how do do I generate random/variable JSON keys with Go's field tags?
I have a feeling the answer is "You don't". What other alternatives do I have then?
In most cases where there are names not known at compile time, a map can be used:
type Resource struct {
AWSInstance map[string]AWSInstance `json:"aws_instance"`
}
type AWSInstance struct {
AMI string `json:"ami"`
Count int `json:"count"`
SourceDestCheck bool `json:"source_dest_check"`
// ... and so on
}
Here's an example showing how to construct the value for marshalling:
r := Resource{
AWSInstance: map[string]AWSInstance{
"web1": AWSInstance{
AMI: "qdx",
Count: 2,
},
},
}
playground example

How to unwrap a list from typesafeconfig list to the list of particular type?

I have a config in json format and I am using Typesafe Config library to load this.
Input config in json format
{
"input": {
"Date": "2014-01-01",
"Ids": ["1","2","3","4"]
}
}
Code
import com.typesafe.config.{Config, ConfigFactory}
val config = ConfigFactory.load("test.json")
val ids = config.getList("input.Ids").unwrapped
# ids: java.util.List[Object] = [1, 2, 3, 4]
All I am getting is list of object. When I try to do a map of each element to int it fails because each element is an object.
ids.map(_.toInt)
<console>:14: error: value toInt is not a member of Object
ids.map(_.toInt)
How to convert the object list to integer list in scala ?
You can use the getStringList method and then map the result to int
config.getStringList("input.Ids").map(_.toInt)
or in this case use the getIntList method directly

updating Json object

I have a json object that I need to update. The original object is a list that looks like this:
[
{
"firstName":"Jane",
"lastName":"Smith"
},
{
"firstName":"Jack",
"lastName":"Brown"
}
]
For each element in the list, we have an extra field, "age", that needs to be added at run-time, so the result should look like the following:
[
{
"firstName":"Jane",
"lastName":"Smith",
"age": "21"
},
{
"firstName":"Jack",
"lastName":"Brown",
"age": "34"
}
]
Any suggestions how to do this so the result is still json?
Thanks.
request.body.asJson.map {
jm => (jm.as[JsObject] ++ Json.obj("age" -> 123))
}
I would recommended deserializing the JSON array you receive into a List of case classes, then having some function fill in the missing attributes based on the current attributes of the case class, and finally serializing them as JSON and serving the response.
Let's make a Person case class with the fields that will be missing as Option:
import play.api.libs.json.Json
case class Person(firstName: String, lastName: String, age: Option[Int])
object Person {
implicit val format: Format[Person] = Json.format[Person]
def addAge(person: Person): Person = {
val age = ... // however you determine the age
person.copy(age = Some(age))
}
}
Within the companion object for Person I've also defined a JSON serializer/deserializer using the format macro, and a stub for a function that will find a person's age then copy it back into the person and return it.
Deep within the web service call you might then have something like this:
val jsArray = ... // The JsValue from somewhere
jsArray.validate[List[Person]].fold(
// Handle the case for invalid incoming JSON
error => InternalServerError("Received invalid JSON response from remote service."),
// Handle a deserialized array of List[Person]
people => {
Ok(
// Serialize as JSON, requires the implicit `format` defined earlier.
Json.toJson(
// Map each Person to themselves, adding the age
people.map(person => Person.addAge(person))
)
)
}
)
This method is much safer, otherwise you'll have to extract values from the array one by one and concatenate objects, which is very awkward. This will also allow you to easily handle errors when the JSON you receive is missing fields you're expecting.