F# - JSON Type Provider - Literal String - f#

I have an interesting problem using the F# Type Provider. Using the following sample json to generate the required type works beautifully but with one glaring issue - one of the values is a literal string ("235") but that string does not occur in the real data - each new record has a different three-digit number:
{
"record": {
"235": {
"Id": "001",
"Name": "A. N. Other",
"IdDatetime": "2017-11-11T13:10:00+00:00"
...
}
"255": {
"Id": "005",
"Name": "D. Other",
"IdDatetime": "2017-11-11T13:10:00+00:00"
...
}
}
So, unfortunately, I cannot access the Id, Name, and IdDateTime fields without knowing the three-digit code in advance!
Any advice?

Do you have to use the JsonProvider? There is a simpler JsonParser in FSharp.Data that allows easier access to more dynamic data. After that it's just a simple exercise to destructure it to get to the inner part.
#r #"../packages/FSharp.Data/lib/net40/FSharp.Data.dll"
open FSharp.Data
open FSharp.Data.JsonExtensions
[<Literal>]
let jsonText = """
{
"record": {
"235": {
"Id": "001",
"Name": "A. N. Other",
"IdDatetime": "2017-11-11T13:10:00+00:00"
}
}
}
"""
let json1 = JsonValue.Parse(jsonText)
let json2 = json1?record
let json3 =
match json2 with
| JsonValue.Record x -> x
let json4 =
match json3 with
[|(_,x)|] -> x
val json4 : JsonValue = { "Id": "001", "Name": "A. N. Other",
"IdDatetime": "2017-11-11T13:10:00+00:00" }
Edit
On an array of jsonvalues this works the same, except you have to map the matching function over. For example:
let jsonText2 = """
{
"record": {
"235": {
"Id": "001",
"Name": "A. N. Other",
"IdDatetime": "2017-11-11T13:10:00+00:00"
},
"255": {
"Id": "005",
"Name": "D. Other",
"IdDatetime": "2017-11-11T13:10:00+00:00"
}
}
}
"""
let json1 = JsonValue.Parse(jsonText2)
let json2 = json1?record
let json3 =
match json2 with
| JsonValue.Record x -> x
let json4 =
json3
|> Array.map (function _,x -> x)
val json4 : JsonValue [] = [|{ "Id": "001", "Name": "A. N.
Other", "IdDatetime": "2017-11-11T13:10:00+00:00" };
{ "Id": "005", "Name": "D. Other", "IdDatetime": "2017-11-11T13:10:00+00:00" }|]

Related

Swift iOS how to handle JSON nested array in object class?

I would like to create a Swift TableView for all available sizes of coffee within a specific type. With this JSON tree, how can you structure the class to create an array for the type specific sizes?
The JSON Tree is structured as follows:
{
"_id": "60ba1ab72e35f2d9c786c610",
"types": [
{
"_id": "60ba1a062e35f2d9c786c56d",
"name": "Ristretto",
"sizes": [
"60ba18d13ca8c43196b5f606",
"60ba3368c45ecee5d77a016b"
],
"extras": [
"60ba197c2e35f2d9c786c525"
]
},
{
"_id": "60be1db3c45ecee5d77ad890",
"name": "Espresso",
"sizes": [
"60ba3368c45ecee5d77a016b",
"60ba33dbc45ecee5d77a01f8"
],
"extras": [
"60ba34a0c45ecee5d77a0263"
]
},
{
"_id": "60be1eabc45ecee5d77ad960",
"name": "Cappuccino",
"sizes": [
"60ba18d13ca8c43196b5f606",
"60ba3368c45ecee5d77a016b",
"60ba33dbc45ecee5d77a01f8"
],
"extras": [
"60ba197c2e35f2d9c786c525",
"60ba34a0c45ecee5d77a0263"
]
}
],
"sizes": [
{
"_id": "60ba18d13ca8c43196b5f606",
"name": "Large",
"__v": 0
},
{
"_id": "60ba3368c45ecee5d77a016b",
"name": "Venti"
},
{
"_id": "60ba33dbc45ecee5d77a01f8",
"name": "Tall"
}
],
"extras": [
{
"_id": "60ba197c2e35f2d9c786c525",
"name": "Select the amount of sugar",
"subselections": [
{
"_id": "60ba194dfdd5e192e14eaa75",
"name": "A lot"
},
{
"_id": "60ba195407e1dc8a4e33b5e5",
"name": "Normal"
}
]
},
{
"_id": "60ba34a0c45ecee5d77a0263",
"name": "Select type of milk",
"subselections": [
{
"_id": "611a1adeff35e4db9df19667",
"name": "Soy"
},
{
"_id": "60ba348d8c75424ac5ed259e",
"name": "Oat"
},
{
"_id": "60ba349a869d7a04642b41f4",
"name": "Cow"
}
]
}
]
}
My classes: (I can currently load a TableView with the coffee sizes, but not the type specific sizes)
import Foundation
import UIKit
class Types: Codable {
let types: [CoffeeType]
init(types: [CoffeeType]) {
self.types = types
}
}
class Sizes: Codable {
let sizes: [CoffeeType]
init(sizes: [CoffeeType]) {
self.sizes = sizes
}
}
class CoffeeType: Codable {
let _id: String
let name: String
init(_id: String, name: String) {
self._id = _id
self.name = name
}
}
The app is structured like this: HomeViewController is a TableView of Types. When you click on a Type, you transition to SelectSizeViewController, to which I have already assigned a Type through the segue. On SelectSizeViewController, I would like to display the list of specific sizes.
use QuickType to get the model this is quite helpful. you will be getting the model easily(suggestion). I think you are using the wrong model.
if your Base model struct is correct it should be something like this
struct Coffie: Codable {
let id: String
let types: [TypeElement]
let sizes: [Size]
let extras: [Extra]
enum CodingKeys: String, CodingKey {
case id
case types, sizes, extras
}
}
from here itself you can get the type and sizes with corresponded id's you can filter size values from sizes array

Group a list of json object by property inside the common array using iOS Swift

I would like to ask if how to group the object by another object inside its common array based on the id in Swift.
Here's the JSON response, I need to group the list of the item by program id.
{
"id": "",
"ordered_by": 64,
"order_details": [
{
"resource": "Product",
"required_prescription": false,
"item": {
"id": 6,
"name": "Synergistic Copper Gloves",
"code": "51537661-C",
"enabled": true,
"generic_name": "Mediocre Steel Wallet",
"price_cents": 200000
},
"program": {
"id": 12, <----PROGRAM ID
"name": "Synergistic Wooden Shoes",
"provider": "Synergistic Rubber Coat",
"discount_type": "fixed"
}
},
{
"resource": "Product",
"required_prescription": true,
"item": {
"id": 7,
"name": "Rustic Leather Table",
"code": "74283131-P",
"enabled": true,
"generic_name": "Incredible Bronze Clock",
"price_cents": 8994
},
"program": {
"id": 12, <----PROGRAM ID
"name": "Synergistic Wooden Shoes",
"provider": "Synergistic Rubber Coat",
"discount_type": "fixed"
}
},
{
"resource": "Product",
"required_prescription": false,
"item": {
"id": 116,
"name": "Ergonomic Marble Hat",
"code": "98845056-A",
"enabled": true,
"generic_name": "Incredible Granite Lamp",
"price_cents": 8267
},
"program": {
"id": 10, <----PROGRAM ID
"name": "Durable Rubber Bag",
"provider": "Aerodynamic Steel Chair",
"discount_type": "fixed"
}
}
]}
For example, the item with program id 12 should be inserted under its common program.
This should be the expected object after grouping. The item was grouped by program id 12 & 10.
[
{
"id": 12, <----- PROGRAM ID
"name": "Synergistic Wooden Shoes",
"provider": "Synergistic Rubber Coat",
"discount_type": "fixed",
"item": [
{
"id": 6,
"name": "Synergistic Copper Gloves",
"code": "51537661-C",
"enabled": true,
"generic_name": "Mediocre Steel Wallet",
"price_cents": 200000
},
{
"id": 7,
"name": "Rustic Leather Table",
"code": "74283131-P",
"enabled": true,
"generic_name": "Incredible Bronze Clock",
"price_cents": 8994
}
]
},
{
"id": 10, <----PROGRAM ID
"name": "Durable Rubber Bag",
"provider": "Aerodynamic Steel Chair",
"discount_type": "fixed",
"item": [
{
"id": 116,
"name": "Ergonomic Marble Hat",
"code": "98845056-A",
"enabled": true,
"generic_name": "Incredible Granite Lamp",
"price_cents": 8267
}
]
}
]
I've successfully made it in Java using the sample code below:
private String parseJson(String source) {
JSONArray result = new JSONArray();
List<Integer> ids = new ArrayList<>();
HashMap<Integer,JSONObject> programs = new HashMap<>();
try {
JSONObject jSource = new JSONObject(source);
JSONArray orderDetails = jSource.getJSONArray("order_details");
if (orderDetails.length() > 0) {
for (int i = 0; i < orderDetails.length(); i++) {
JSONObject jsonObject = orderDetails.getJSONObject(i);
JSONObject item = jsonObject.getJSONObject("item");
JSONObject program = jsonObject.getJSONObject("program");
int programId = jsonObject.getJSONObject("program").getInt("id");
if (!ids.contains(programId)) {
ids.add(programId);
program.put("item",new JSONArray().put(item));
programs.put(programId,program);
}else{
program.put("item",programs.get(programId).getJSONArray("item").put(item));
}
}
for(int k :programs.keySet()){
result.put(programs.get(k));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return result.toString();
}
I'm trying to replicate it on swift but I'm looking for a different approach, not the same approach as I did in Java. I tried to used reduce and filter method but it doesn't work.
Any answer would be hight appreciated.
Thanks
Few comments on an answer from #omerfarukozturk,
Flatmap is deprecated so you can use map or compactMap.
In omerfarukozturk's answer he returns only program id while grouping which is not as per requirement, we need whole program details instead of just Id
So I have applied some other logic.
Bind data with model
let arrDetails = //Bind data with model, response.orderDetails
Get Unique program ID
let arrProgramID = arrDetails.compactMap{ $0.program }.removingDuplicates(byKey: \.id)
// removingDuplicates is logic for remove duplicate programs and have unique program array with all information
Bind array in [[Program: [Item]]] form
let finalArray = arrProgram.map { (program) -> [Program: [Item]] in
let arr = arrDetails.filter{ $0.program.id == program.id }.compactMap{ $0.item }
return [program: arr]
}
Adding extension for removing duplicates
extension Array {
func removingDuplicates<T: Equatable>(byKey key: KeyPath<Element, T>) -> [Element] {
var result = [Element]()
var seen = [T]()
for value in self {
let key = value[keyPath: key]
if !seen.contains(key) {
seen.append(key)
result.append(value)
}
}
return result
}
}
You can use Dictionary(grouping:) to group a list. Not completely answer, but for your case you can create a logic like below;
Assume you have a decoded response model as response for your json.
let flatttenOrderDetails = response.order_details.flatMap( { $0 })
let grouped = Dictionary(grouping: flatttenOrderDetails, by: { (element: Detail) in
return element.program.id
})
I was able to group the JSON based on its common program id by creating another model ProgramWith items.
struct ProgramWithItems: Codable {
let id: Int?
let name: String?
let provider: String?
let discountType: String?
let resource: String?
let discount: String?
let item: [Item]
}
Then I used the reduce method and populate the ProgramWithItems model using the code below:
func groupItems(_ response: CartEntity.Response) -> [ProgramWithItems] {
response.orderDetails!.reduce(into: []) { (result: inout [ProgramWithItems], detail: OrderDetail) in
guard let index = result.firstIndex(where: { $0.id == detail.program?.id }) else {
var item = detail.item
item?.quantity = detail.quantity
item?.costCents = detail.cost_cents
let newProgram = ProgramWithItems(
id: detail.program?.id,
name: detail.program?.name ?? "",
provider: detail.program?.provider ?? "",
discountType: detail.program?.discountType ?? "",
resource: detail.resource ?? "",
discount: detail.program?.discount,
item: [item!])
result.append(newProgram)
return
}
let existingProgram = result[index]
var item = detail.item
item?.quantity = detail.quantity
item?.costCents = detail.cost_cents
let extendedProgram = ProgramWithItems(
id: existingProgram.id,
name: existingProgram.name,
provider: existingProgram.provider,
discountType: existingProgram.discountType,
resource: detail.resource ?? "",
discount: detail.program?.discount,
item: existingProgram.item + [item!])
result[index] = extendedProgram
}
}

Filter Data in SwiftyJson

I have one SwiftyJson object.
I can not filter that array. I have tried this solution https://stackoverflow.com/a/37497170/4831567. But my json format is different that's why not working.
[
{
"name": "19860",
"header": {
"start_time": "1519270200",
"end_time": "1519299000",
"state": "lunch"
},
"alerts": "1",
"venue": {
"location": "Delhi, India",
"timezone": "+05:30"
},
"srs_category": [
0,
1
]
},
{
"name": "19861",
"header": {
"start_time": "1519270200",
"end_time": "1519299000",
"state": "Dinner"
},
"alerts": "1",
"venue": {
"location": "Mumbai, India",
"timezone": "+05:30"
},
"srs_category": [
1,
3
]
},
{
"name": "19862",
"header": {
"start_time": "1519270200",
"end_time": "1519299000",
"state": "lunch"
},
"alerts": "1",
"venue": {
"location": "Surat, India",
"timezone": "+05:30"
},
"srs_category": [
0,
2
]
}
]
i want to find that object that srs_category contain 1. I know it is possible by looping and condition. But i want via NSPredicate. If it is possible then please help me.
Thank You.
Here is easy way to use SwiftyJSON:
let filtered = JSON(yourArray).arrayValue.filter({
$0["srs_category"].arrayValue.map({ $0.intValue }).contains(1)
})
Use a Swift native function rather than NSPredicate
data represents the Data object received from somewhere
do {
if let json = try JSONSerialization.jsonObject(with:data) as? [[String:Any]] {
let srsCategory1 = json.first(where: { dict -> Bool in
guard let array = dict["srs_category"] as? [Int] else { return false }
return array.contains(1)
})
print(srsCategory1 ?? "not found")
}
} catch {
print(error)
}
If there are multiple items which can match the condition replace first with filter. Then the result is a non-optional array.

How to return relationship type with Neo4J's Cypher queries?

I am trying to get the relationship type of a very simple Cypher query, like the following
MATCH (n)-[r]-(m) RETURN n, r, m;
Unfortunately this return an empty object for r. This is troublesome since I can't distinguish between the different types of relationships. I can monkey patch this by adding a property like [r:KNOWS {type:'KNOWS'}] but I am wondering if there isn't a direct way to get the relationship type.
I even followed the official Neo4J tutorial (as described below), demonstrating the problem.
Graph Setup:
create (_0 {`age`:55, `happy`:"Yes!", `name`:"A"})
create (_1 {`name`:"B"})
create _0-[:`KNOWS`]->_1
create _0-[:`BLOCKS`]->_1
Query:
MATCH p=(a { name: "A" })-[r]->(b)
RETURN *
JSON RESPONSE BODY:
{
"results": [
{
"columns": [
"a",
"b",
"p",
"r"
],
"data": [
{
"row": [
{
"name": "A",
"age": 55,
"happy": "Yes!"
},
{
"name": "B"
},
[
{
"name": "A",
"age": 55,
"happy": "Yes!"
},
{},
{
"name": "B"
}
],
{}
]
},
{
"row": [
{
"name": "A",
"age": 55,
"happy": "Yes!"
},
{
"name": "B"
},
[
{
"name": "A",
"age": 55,
"happy": "Yes!"
},
{},
{
"name": "B"
}
],
{}
]
}
]
}
],
"errors": []
}
As you can see, I get an empty object for r, which makes it impossible to distinguish between the relationships.
NOTE: I am running Neo4J v.2.2.2
Use the type() function.
MATCH (n)-[r]-(m) RETURN type(r);
Added distinct.
MATCH (n)-[r]-(m) RETURN distinct type(r);

Mongo query in Object of arrays

Document example
{
"_id": 1,
"test": {
"item_obj": {
"item1": ["a", "b"],
"item2": ["c"],
"item3": ["a", "d"]
}
}
}
I want to fetch documents where "a" exists in test.item_obj. "a" may exist in any array. And we don't know the keys present inside item_obj (No idea item1, item2 or item3 exists).
Need rails-mongo query.
If this is your search case, then whatever way you look at it you need the JavaScript evaluation of the $where clause to resolve your current structure. In the shell example ( since you need to use the JavaScript expression anyway ):
db.collection.find(function() {
var root = this.test.item_obj;
return Object.keys(root).some(function(key) {
return root[key] == "a";
});
})
Or for mongoid that is something like:
func = <<-eof
var root = this.test.item_obj;
return Object.keys(root).some(function(key) {
return root[key] == "a";
});
eof
Model.for_js(func)
However, if you simply change your structure to define "items_objects" as an array as follows:
{
"_id": 1,
"test": {
"item_objects": [
{ "name": "item1", "data": ["a","b"] },
{ "name": "item2", "data": ["c"] },
{ "name": "item3", "data": ["a","d"] }
}
}
}
Then asking for what you want here is as basic as:
db.collection.find({
"test.item_objects.data": "a"
})
Or for mongoid:
Model.where( "test.item_objects.data" => "a" )
Nested arrays are not really a great idea though, so perhaps live with:
{
"_id": 1,
"test": {
"item_objects": [
{ "name": "item1", "data": "a" },
{ "name": "item1", "data": "b" },
{ "name": "item2", "data": "c" },
{ "name": "item3", "data": "a" },
{ "name": "item3", "data": "d" }
}
}
}
Which is basically the same thing, but a but more long winded. But ultimately much more easy to deal with in atomic updates. And of course the query to find the values in the document is exactly the same.

Resources