Parsing nested arrays in JSON data swift - json

I'm currently working with trying to extract bits of information from a complicated json based database. After NSJSONSerialization.JSONObjectWithData I get output like follows (some returns added for clarity)
[
"title": Recorder Suite In A Minor - Viola Concerto - Tafelmusik,
"estimated_weight": 85,
"year": 0,
"thumb": ,
"identifiers": <__NSArrayI 0x600000089970>(
{
description = Text;
type = Barcode;
value = 4891030501560;
},
{
description = Printed;
type = Barcode;
value = "4 891030 501560";
},
{
type = ASIN;
value = B0000013L9;
},
{
type = "Mould SID Code";
value = "ifpi 8412";
},
{
type = "Matrix / Runout";
value = "CD PLANT AB 8550156 CDM01";
},
{
description = "SPARS Code";
type = Other;
value = DDD;
},
{
type = "Label Code";
value = "LC 9158";
}
),
"id": 885370,
"date_changed": 2014-06-17T03:53:03-07:00,
"master_url": https://api.discogs.com/masters/495830,
etc … ]
In particular, I need to know how to get the information out of the nested array. Note that the array is not (obviously) a nested dictionary - given the equal signs and the repeated keys. Any help with how to parse this would be appreciated.

I would use a Pod like SwiftyJSON.
First, you need to install CocoaPods, and then go for SwiftyJSON.
I would parse nested arrays in the following manner:
let json = JSON(data: dataFromNetworking)
if let items = json["items"].array {
for item in items {
if let title = item["title"].string {
println(title)
}
}
}
Check out the documentation and Usage section of SwiftyJSON for more info.
Cheers...

Related

Rescript manipulate JSON file

I have this JSON file.
Using rescript I want to :
Read the file.
Extract data from the file.
Write result in a new file.
{
"name": "name",
"examples": [
{
"input": [1,2],
"result": 1
},
{
"input": [3,4],
"result": 3
}
],
}
I was able to acheive this using JavaScript
var file = Fs.readFileSync("file.json", "utf8");
var data = JSON.parse(file);
var name = data.name
var examples = data.examples
for (let i = 0; i< examples.length; i++){
let example = examples[i]
let input = example.input
let result = example.result
let finalResult = `example ${name}, ${input[0]}, ${input[1]}, ${result} \n`
Fs.appendFileSync('result.txt',finalResult)
}
These are my attempts at writing it in Rescript and the issues I ran into.
let file = Node.Fs.readFileSync("file.json", #utf8)
let data = Js.Json.parseExn(file)
let name = data.name //this doesn't work. The record field name can't be found
So I have tried a different approach (which is a little bit limited because I am specifying the type of the data that I want to extract).
#module("fs")
external readFileSync: (
~name: string,
[#utf8],
) => string = "readFileSync"
type data = {name: string, examples: array<Js_dict.t<t>>}
#scope("JSON") #val
external parseIntoMyData: string => data = "parse"
let file = readFileSync(~name="file.json", #utf8)
let parsedData = parseIntoMyData(file)
let name = parsedData.name
let example = parsedData.examples[0]
let input = parsedData.examples[0].input //this wouldn't work
Also tried to use Node.Fs.appendFileSync(...) and I get The value appendFileSync can't be found in Node.Fs
Is there another way to accomplish this?
It's not clear to me why you're using Js.Dict.t<t> for your examples, and what the t in that type refers to. You certainly could use a Js.Dict.t here, and that might make sense if the shape of the data isn't static, but then you'd have to access the data using Js.Dict.get. Seems you want to use record field access instead, and if the data structure is static you can do so if you just define the types properly. From the example you give, it looks like these type definitions should accomplish what you want:
type example {
input: (int, int), // or array<int> if it's not always two elements
result: int,
}
type data = {
name: string,
examples: array<example>,
}

Parsing JSON error cannot read property 'url' of undefined

I have been trying to parse JSON, which have 3 different set of data where one element have various number of children and sometimes none. I am getting an error when there is no children present or only one present. I declared the JSON as var data.
JSON A
{
"floorplan": [
{
"title": "plan1",
"url": "https://media.plan1.pdf"
},
{
"title": "plan2",
"url": "https://media.plan2.pdf"
}
]
}
JSON B
{"floorplan": []}
JSON C
{
"floorplan": [
{
"title": "plan1",
"url": "https://media.plan1.pdf"
}
]
}
I parsed the JSON like this:
var items = JSON.parse(data);
return {
floorplan1: items.floorplan[0].url;
floorplan2: items.floorplan[1].url;
}
But, it only returned data for the JSON A, for other 2 it gave TypeError: Cannot read property 'url' of undefined.
I modified the code to check if floorplan have at least one child and then parse data.
var items = JSON.parse(data);
var plan = items.floorplan[0];
if(plan){
return {
floorplan1: items.floorplan[0].url;
floorplan2: items.floorplan[1].url;
}
}
The new code returned data for JSON A and B(as empty row), but gave error for C. C have one child still it got the error.
I also tried this code, still got the error for JSON C.
var items = JSON.parse(data);
var plan = items.floorplan[0];
var plan1;
var plan2;
if(plan){
plan1 = items.floorplan[0].url;
plan2 = items.floorplan[1].url;
}
return{
floorplan1 : plan1 ? plan1 : null;
floorplan2 : plan2 ? plan2 : null;
}
Is there any method I can try to get data returned for all 3 types of JSON?
let data = `
[{"floorplan": [{
"title": "plan1",
"url": "https://media.plan1.pdf"
}, {
"title": "plan2",
"url": "https://media.plan2.pdf"
}]},
{"floorplan": []},
{"floorplan": [{
"title": "plan1",
"url": "https://media.plan1.pdf"
}]}]`;
let json = JSON.parse(data);
//console.log(json);
json.forEach(items=>{
//console.log(items);
let o = {
floorplan1: items.floorplan.length > 0 ? items.floorplan[0].url : '',
floorplan2: items.floorplan.length > 1 ? items.floorplan[1].url : ''
};
console.log(o);
o = {
floorplan1: (items.floorplan[0] || {'url':''}).url,
floorplan2: (items.floorplan[1] || {'url':''}).url
};
console.log(o);
o = {
floorplan1: items.floorplan[0]?.url,
floorplan2: items.floorplan[1]?.url
};
console.log(o);
const {floorplan: [one = {url:''}, two = {url:''}]} = items;
o = {
floorplan1: one.url,
floorplan2: two.url
};
console.log(o);
});
Sure. A few ways, and more than I have here. I have put all the raw data into one string, parsed it into json and then iterated through that. In each loop my variable items will correspond to one of the json variables you created and referenced in your question as items.
In the first example, I check to make sure that items.floorplan has at least enough elements to contain the url I'm trying to reference, then use the ternary operator ? to output that URL if it exists or an empty string if it doesn't.
In the second example, I use the || (OR) operator to return the first object that evaluates to true. If items.floorplan[x] exists, then it will be that node, and if it doesn't I provide a default object with an empty url property on the right hand side, and then just use the url from the resulting object.
In the third, I use the optional chaining operator that was introduced in 2020. This method will return undefined if the url doesn't exist.
In the fourth example, I use destructuring to pull values out of the items variable, and make sure that there is a default value for url in case the items variable doesn't have a corresponding value.
But there are many more ways to go about it. These are just a few, and you can't necessarily say which approach is better. It's dependent on your intent and environment. With the exception of optional chaining (which shows undefined if the property doesn't exist), you can see these produce the same results.
DOCS for optional chaining: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
DOCS for destructuring: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
An article on destructuring: https://javascript.info/destructuring-assignment

JSON serialization of optional values with FsPickler

Is it possible to serialize optional values in F# using FsPickler such that:
when the value is Some(), the value contained is serialized
and when it is None, it does not get serialized at all?
With the following example code:
type Person = { name: string; age: int option }
let data = [
{ name = "Helena"; age = Some(24) };
{ name = "Peter"; age = None }
]
let jsonSerializer = FsPickler.CreateJsonSerializer(true, true)
let streamWriter = new StreamWriter(#"C:\output.json")
let out = jsonSerializer.SerializeSequence(streamWriter, data)
the output.json file contains the following JSON:
[
{
"name": "Helena",
"age": {
"Some": 24
}
},
{
"name": "Peter",
"age": null
}
]
But I would like the contents of JSON file to look like this instead:
[
{
"name": "Helena",
"age": 24
},
{
"name": "Peter"
}
]
I am using FsPickler.Json v3.2.0 and Newtonsoft.Json v9.0.1.
UPDATE (January 11, 2017): Using the script in the gist linked by Stuart in the answer below, I got it working like this:
let obj = { name = "Peter"; age = None }
let stringWriter = new StringWriter()
let jsonSerializer = new JsonSerializer()
jsonSerializer.Converters.Add(new IdiomaticDuConverter())
jsonSerializer.NullValueHandling <- NullValueHandling.Ignore
jsonSerializer.Serialize(stringWriter, obj)
Using Newtonsoft.Json you can use the gist here (credit: Isaac Abraham) to give you the behaviour you are after.
It's funny, just moments before you posted this, I was looking to see if the same thing exists within FsPickler.Json, but came to no conclusions. You could use TypeShape which is used in FsPickler to make the gist cleaner, but not sure if FsPickler.Json can do this for you out of the box.
I'm the author FsPickler, so thought I'd repost a response I gave in a similar issue.
No, managing the shape of the serialization formats is beyond the design goals of this library. While you could use pickler combinators to influence how serialized types look like, this will only take you so far.
With FSharp.Json library it would work just well:
open FSharp.Json
type Person = { name: string; age: int option }
let data = [
{ name = "Helena"; age = Some(24) };
{ name = "Peter"; age = None }
]
let json = Json.serialize data
This produces following JSON:
[
{ "name": "Helena", "age": 24 },
{ "name": "Peter", "age": null }
]
How option None is serialized is configurable. Here's how to omit the option member that has None value, pay attention to config:
let config = JsonConfig.create(serializeNone=SerializeNone.Omit)
let json = Json.serializeEx config data
Disclosure: I'm author of FSharp.Json library.

UWP - From Json string to Structure (Classes)

I receive a JSon string from WS. It's so long that I can't use Json2charp to parse it and receive the structurated class.
I want to parse the string with a command. How is it possible?
I don't know the classes so I can't use a command like:
Dim result = JsonConvert.DeserializeObject(Of MyClass.RootObject)(String_From_File)
Is it possible from the string to obtain the class without using json2charp site ?
For example, in vs.net if on the variable 'string_from_file' I choose 'Json Visualizer' and see all classes and data in correct mode.
How can I obtain the same in my code ?
I have installed Newtonsoft.json
If you cannot use the json to class mappers like NewtonSoft.Json. You can use the Windows.Data.Json api. It let you parse and extract the data you want from your JSON string.
JsonValue jsonValue = JsonValue.Parse("{\"Width\": 800, \"Height\": 600, \"Title\": \"View from 15th Floor\", \"IDs\": [116, 943, 234, 38793]}");
double width = jsonValue.GetObject().GetNamedNumber("Width");
double height = jsonValue.GetObject().GetNamedNumber("Height");
string title = jsonValue.GetObject().GetNamedString("Title");
JsonArray ids = jsonValue.GetObject().GetNamedArray("IDs");
You can find a sample in the Windows Universal Sample GitHub.
A complex object parsing is shown here. I've extracted the most relevant parts here. The JSON string is provided to the User constructor which is extracting what it needs and then delegating the parsing to the nested School constructor.
{
"id": "1146217767",
"phone": null,
"name": "Satya Nadella",
"education": [
{
"school": {
"id": "204165836287254",
"name": "Contoso High School"
},
"type": "High School"
},
{
"school": {
"id": "116138758396662",
"name": "Contoso University"
},
"type": "College"
}
],
"timezone": -8,
"verified": true
}
This JSON fragment is parsed with this code:
public User(string jsonString) : this()
{
JsonObject jsonObject = JsonObject.Parse(jsonString);
Id = jsonObject.GetNamedString(idKey, "");
IJsonValue phoneJsonValue = jsonObject.GetNamedValue(phoneKey);
if (phoneJsonValue.ValueType == JsonValueType.Null)
{
Phone = null;
}
else
{
Phone = phoneJsonValue.GetString();
}
Name = jsonObject.GetNamedString(nameKey, "");
Timezone = jsonObject.GetNamedNumber(timezoneKey, 0);
Verified = jsonObject.GetNamedBoolean(verifiedKey, false);
foreach (IJsonValue jsonValue in jsonObject.GetNamedArray(educationKey, new JsonArray()))
{
if (jsonValue.ValueType == JsonValueType.Object)
{
Education.Add(new School(jsonValue.GetObject()));
}
}
}
public School(JsonObject jsonObject)
{
JsonObject schoolObject = jsonObject.GetNamedObject(schoolKey, null);
if (schoolObject != null)
{
Id = schoolObject.GetNamedString(idKey, "");
Name = schoolObject.GetNamedString(nameKey, "");
}
Type = jsonObject.GetNamedString(typeKey);
}
If you cannot use the automatic mapping from NewtonSoft.Json, you have no other way than doing it yourself.
Is not so simple.
The Json i received is very complicated and have many class
So i can't use
double width = jsonValue.GetObject().GetNamedNumber("Width");
Inside class i have more ...

How can i list the values of a Node in JSON ?

Say, I have a JSON that has an array of "Topics"
I need to list all "created_at" values of all the topics
without the other data , using the Chrome console
P.S : I'm Using JSONView
You can loop through the objects in your array and simply access the property created_at.
Example
var json = {
all_topics: [{
"created_at:" "2016-08-08T10:22:03.123Z",
"name": "topic1"
}, {
"created_at": "2016-08-08T11:43:06.963Z",
"name": "topic2"
}]
}
for (var topic of json.all_topics) {
console.log(topic.created_at);
}
You can use JSON.stringify to turn a JavaScript object into a JSON String, and JSON.parse to turn a JSON string into a JavaScript object.
var jsonString = JSON.stringify(json);
==> {"all_topics":[{"created_at":"2016-08-08T10:22:03.123Z","name":"topic1"},{"created_at":"2016-08-08T11:43:06.963Z","name":"topic2"}]}
var jsonObj = JSON.parse(jsonString);
==> Object {all_topics: Array[2]}
Alternatively, you could return a new array with the filtered property using Array.prototype.map:
var topics = json.all_topics.map(function(obj){
return obj.created_at;
});
==> ["2016-08-08T10:22:03.123Z", "2016-08-08T11:43:06.963Z"]