Accessing Values in Deeply Nested Array - Ruby on Rails - ruby-on-rails

I am using an API call which returns a JSON response. I want to access the data inside the response so I can create some nice display cards showing the info and pictures. Here is a snippet from the response, the response property is populated with about 20 objects I'll include just two for brevity:
{
"success": true,
"message": "",
"result": [
{
"MarketCurrency": "LTC",
"BaseCurrency": "BTC",
"MarketCurrencyLong": "Litecoin",
"BaseCurrencyLong": "Bitcoin",
"MinTradeSize": 1e-8,
"MarketName": "BTC-LTC",
"IsActive": true,
"Created": "2014-02-13T00:00:00",
"Notice": null,
"IsSponsored": null,
"LogoUrl": "https://i.imgur.com/R29q3dD.png"
},
{
"MarketCurrency": "DOGE",
"BaseCurrency": "BTC",
"MarketCurrencyLong": "Dogecoin",
"BaseCurrencyLong": "Bitcoin",
"MinTradeSize": 1e-8,
"MarketName": "BTC-DOGE",
"IsActive": true,
"Created": "2014-02-13T00:00:00",
"Notice": null,
"IsSponsored": null,
"LogoUrl": "https://i.imgur.com/e1RS4Hn.png"
},
In my Rails controller I'm using JSON.parse and I'm trying to turn it into an object with the Open struct option:
#markets = JSON.parse(markets.to_json, object_class: OpenStruct)
In my view I'll do this <%=#markets.class%> and it shows Array and not object. So I try this <%=#markets.size%> and it shows 1. If I do <%=#markets[0]['success']%> I would expect it to return true but it returns 'success'. So, I'm not understanding why the ostruct library isn't working like I would expect or how I can get to the objects stored in the result array. Any help is greatly appreciated!

You already have a JSON response, isn't needed to use to_json again, try just parsing that object, and then use the dot . to access its fields, as an OpenStruct object now then you can access them as methods:
require 'json'
a = '{
"success": true,
"message": "",
"result": [{
"MarketCurrency": "LTC",
"BaseCurrency": "BTC",
"MarketCurrencyLong": "Litecoin",
"BaseCurrencyLong": "Bitcoin",
"MinTradeSize": 1e-8,
"MarketName": "BTC-LTC",
"IsActive": true,
"Created": "2014-02-13T00:00:00",
"Notice": null,
"IsSponsored": null,
"LogoUrl": "https://i.imgur.com/R29q3dD.png"
}, {
"MarketCurrency": "DOGE",
"BaseCurrency": "BTC",
"MarketCurrencyLong": "Dogecoin",
"BaseCurrencyLong": "Bitcoin",
"MinTradeSize": 1e-8,
"MarketName": "BTC-DOGE",
"IsActive": true,
"Created": "2014-02-13T00:00:00",
"Notice": null,
"IsSponsored": null,
"LogoUrl": "https://i.imgur.com/e1RS4Hn.png"
}]
}'
b = JSON.parse(a, object_class: OpenStruct)
p b.success
# => true

After much debugging and some assistance, I was able to get it to work. The response from the API call was an array with one item. The item was a long string of the entire dataset.
In order to get the expected behavior of "true" when calling #markets.success, I first had to
raw_markets = JSON.parse(markets.to_json)
followed by
#markets = raw_markets.map do |market|
JSON.parse(market, object_class: OpenStruct)
Note: the variable markets holds the original api call:
markets = open('url-to-api')
After this I would get #markets.success = "true" and #markets.result[0] held the first result, #markets.result[1] held the second result, and so on.

Related

Access Nested Netsuite Ruby Hash

I am trying to access a Netsuite Ruby Hash (Netsuite gem) and return orders which need updating from the db, the process uses two service objects, one to get the orders from db, (ListOrdersService), and the this file to compare those against modified ones in Netsuite. Is all working except I am having problems getting some of the nested values in Netsuite in to the output. Code is below with a troublesome item commented out. Its just an each method which compares dates then puts the needed orders in returned value.
def process_order_updates
get_order_updates = []
# Get all our open orders from DB
bj_open_orders = ListOrdersService.new.call
# Get all identical open orders from Netsuite
bj_open_orders.each do |item|
netsuite_sales_orders = NetSuite::Records::SalesOrder.get(item['sales_order_internal_id'])
# Compare the last modified date from Netsuite to the last checked date from app DB
if netsuite_sales_orders.present? && netsuite_sales_orders.last_modified_date > item['last_checked_date']
# If the last modified date is newer, then we create a new hash with the updated order info
get_order_updates << {
sales_order_internal_id: item['sales_order_internal_id'],
order_status: item['order_status']
# quantity_fulfilled: item['items_list']['item']['quantity_fulfilled']
}
puts "still open order #{item['sales_order_internal_id']} needs to be updated, it was last checked by at #{item['last_checked_date']} but it was just modified, on #{netsuite_sales_orders.last_modified_date}"
end
end
puts "Here are the orders that need to be updated: #{get_order_updates}"
end
The Netsuite file code I am referencing is below, and trying to get quantity_fulfilled, quantity_billed, and some others in the file. items_list is a top level item
"item_list": {
"list": [
{
"attributes": {
"item": {
"internal_id": "110",
"external_id": null,
"type": null,
"attributes": {
"name": "000002 Kerosene (UN1223) 3.PGIII (D/E)"
}
},
"expand_item_group": false,
"quantity": "1000.0",
"units": {
"internal_id": "1",
"external_id": null,
"type": null,
"attributes": {
"name": "ltr"
}
},
"description": "Kerosene (UN1223) 3.PGIII (D/E)",
"price": {
"internal_id": "-1",
"external_id": null,
"type": null,
"attributes": {}
},
"rate": "0.81",
"amount": "810.0",
"is_closed": false,
"gross_amt": "850.5",
"line": "1",
"cost_estimate_type": "_averageCost",
"cost_estimate": "900.79",
"quantity_back_ordered": "0.0",
"quantity_billed": "0.0",
"quantity_committed": "1000.0",
"quantity_fulfilled": "0.0",
"tax1_amt": "40.5",
"tax_code": {
"internal_id": "2214",
"external_id": null,
"type": null,
"attributes": {
"name": "VAT:RDR-5%"
}
},
Any tips on how to get those items, directly or with a hashmap welcome Thanks

How do I safely parse data from a Ruby Hash?

I'm parsing a JSON result into a Ruby hash. The JSON result looks like this:
{
"records": [
{
"recordName": "7DBC4FAD-D18C-476A-89FB-14A515098F34",
"recordType": "Media",
"fields": {
"data": {
"value": {
"fileChecksum": "ABCDEFGHIJ",
"size": 9633842,
"downloadURL": "https://cvws.icloud-content.com/B/ABCDEF"
},
"type": "ASSETID"
}
},
"recordChangeTag": "ii23box2",
"created": {
"timestamp": 1449863552482,
"userRecordName": "_abcdef",
"deviceID": "12345"
},
"modified": {
"timestamp": 1449863552482,
"userRecordName": "_abcdef",
"deviceID": "12345"
}
}
]
}
I can't guarantee that it'll return with any/all those values, or that each value will be of a certain type (e.g. Array, Hash, string, number), and if I call it incorrectly then I get a crash.
Right now I need the downloadURL for the first item in the 'records' array, or to write it as I might with the Swift library SwiftyJSON (which I'm far more familiar with):
json["records"][0]["fields"]["data"]["value"]["downloadURL"]
I'm wondering what the safest/best/standard way to do this safely in Ruby is. Perhaps I'm thinking about it wrong?
In ruby 2.3 and above you can use Hash#dig and Array#dig
json = JSON.parse(...)
json.dig('records', 0, 'fields', 'data', 'value', 'downloadURL')
You'll get nil if any of the intermediate values is nil. If one of the intermediate values doesn't have a dig method, for example if `json['records'][0]['fields'] was unexpectedly an integer this will raise TypeError.
From the documentation (http://ruby-doc.org/stdlib-2.2.3/libdoc/json/rdoc/JSON.html):
require 'json'
my_hash = JSON.parse('{"hello": "goodbye"}')
puts my_hash["hello"] => "goodbye"
If you're worried that you might not have some data. See this question:
Equivalent of .try() for a hash to avoid "undefined method" errors on nil?
You can recursively search each object contained in the json object using
the recurse_proc method of the JSON module.
Here is an example using the data you provided.
require 'json'
json_string = '{
"records": [
{
"recordName": "7DBC4FAD-D18C-476A-89FB-14A515098F34",
"recordType": "Media",
"fields": {
"data": {
"value": {
"fileChecksum": "ABCDEFGHIJ",
"size": 9633842,
"downloadURL": "https://cvws.icloud-content.com/B/ABCDEF"
},
"type": "ASSETID"
}
},
"recordChangeTag": "ii23box2",
"created": {
"timestamp": 1449863552482,
"userRecordName": "_abcdef",
"deviceID": "12345"
},
"modified": {
"timestamp": 1449863552482,
"userRecordName": "_abcdef",
"deviceID": "12345"
}
}
]
}'
json_obj = JSON.parse(json_string)
JSON.recurse_proc(json_obj) do |obj|
if obj.is_a?(Hash) && obj['downloadURL']
puts obj['downloadURL']
end
end
Update Based on Frederick's answer and Cary's comment
I originally assumed you just wanted to find the downloadURL somewhere in the json without crashing, but based on Frederick's answer and Cary's comment, it's reasonable to assume that you only want to find the downloadURL if it is at the exact path, rather than if it just exists. Building on Frederick's answer and Cary's comment here are a couple of other options that should safely find the downloadURL at the expected path.
path = ['records', 0, 'fields', 'data', 'value', 'downloadURL']
parsed_json_obj = JSON.parse(json_string)
node_value = path.reduce(parsed_json_obj) do |json,node|
if json.is_a?(Hash) || (json.is_a?(Array) && node.is_a?(Integer))
path = path.drop 1
json[node]
else
node unless node == path.last
end
end
puts node_value || "not_found"
path = ['records', 0, 'fields', 'data', 'value', 'downloadURL']
begin
node_value = parsed_json_obj.dig(*path)
rescue TypeError
node_value = "not_found"
end
puts node_value || "not_found"
BTW, this assumes the json is at least valid, if that is not a given you might want to wrap the JSON.parse in a begin-rescue-end block as well.

Rest-Assured Get jsonPath when JSON key starts with number

How do i get name=status using json path ... problem here is key=2 is random number,,, is their any way to skip these random and read name
Am using rest assured ,,this is sample response on GET request
Response
{
"error": false,
"message": "",
"data": {
"2": {
"name": "No Status",
"protected": "1",
"id": "1",
"temporal_start": "0",
"temporal_end": "2147483647"
},
"3": {
"name": "Started",
"protected": "1",
"id": "2",
"temporal_start": "0",
"temporal_end": "2147483647"
},
}
}
my request code is
given()
.param("error", "false")
.when()
.get(URI)
.then()
.body("data.2.name", startsWith(No))
I've found a solution but it's not very elegant:
when().
get(URI).
then().
body("data.collect { it.value }.reverse()[0].name", equalTo("No Status")).
body("data.collect { it.value }.reverse()[1].name", equalTo("Status"));
Which can be simplified using root paths:
when().
get(URI).
then().
root("data.collect { it.value }.reverse()[%d].name").
body(withArgs("0"), equalTo("No Status")).
body(withArgs("1"), equalTo("Status"));
Explanation:
Since data is a JsonObject represented as a HashMap we run the collect method to return only the values of the Map as a List. Then we reverse the list since it seems like the last when running collect the resulting list will have the last value first. Then we get the first value from this list (data.2 in your example) and finally get the name.

Breeze.js - nonscalar complex properties cause circular structure exception during em.exportEntities

This issue, with scalar complex properties, was reported earlier and resolved in breeze 1.3.5.
I am still seeing it, with non-scalar complex properties, in breeze 1.4.5. After creating an entity using this metadata, the exportEntities() method on the entity manager fails with an exception in JSON.stringify, complaining about a circular reference.
Here's some code to replicate the problem:
var jsonMetadata = {
"metadataVersion": "1.0.5",
"namingConvention": "camelCase",
"localQueryComparisonOptions": "caseInsensitiveSQL",
"dataServices": [{"serviceName": "breeze/myservice/" } ],
"structuralTypes": [
{
"shortName": "Address",
"namespace": "mynamespace",
"isComplexType": true,
"dataProperties": [
{"name": "street"},
{"name": "city"},
]
},
{
"shortName": "Person",
"namespace": "mynamespace",
"autoGeneratedKeyType": "Identity",
"defaultResourceName": "Person",
"dataProperties": [
{"name": "_id", "dataType": "MongoObjectId", "isNullable": false, "defaultValue": "",
"isPartOfKey": true },
{"name": "displayName", "dataType": "String"},
{ "name": "addresses",
"complexTypeName": "Address:#mynamespace",
"isScalar": false
}
]
}
],
"resourceEntityTypeMap": {
"Person": "Person:#mynamespace"
}};
var manager = new breeze.EntityManager();
manager.metadataStore.importMetadata(jsonMetadata);
var person = manager.createEntity('Person', {displayName: "Joe Bob"});
var myAddresses = person.getProperty('addresses');
var myAddressProp = manager.metadataStore.getEntityType("Address").createInstance(
{street: "Main", city:"Pleasantville"});
myAddresses.push(myAddressProp);
console.log("Complex property is a circular datatype, cannot convert to JSON - that's fine")
//JSON.stringify(person.addresses); // fails with error
console.log("... except that manager.exportEntities() doesn't handle that case!");
var entities = manager.exportEntities(); // also fails
The circular reference that JSON.stringify is complaining about seems to be in the 'parent' property of the ComplexAspect of the Address property.
Also, if there's a simpler way to populate the addresses array, I'd appreciate some help.
Ok, this should be fixed as of Breeze v 1.4.6 ( or later) available now
------------- Original Post ------------------
This is a bug. It will be fixed in the next release, out later this week or early next week. and... thanks for the repro. I will post back when it gets in.

How do I add JSON data in the child table against the parent table in Ruby on Rails?

I have been struggling with something in Ruby on Rails.
I have four tables which are interlinked: A, B, C, and D. A is the parent for B and B is the parent for C and D.
I have a records already existing in table B and want to add multiple entries against a particular record, for example "3", in the 'C' and 'D' tables against this id.
The data format is:
[{\"waypoint\":{\"latitude\":37.3645616666667,\"timestamp\":\"2012-10-16T09:58:50Z\",\"background\":false,\"estimated_speed\":17.4189262390137,\"journey_id\":null,\"longitude\":-112.850676666667}},{\"waypoint\":{\"latitude\":37.3648733333333,\"timestamp\":\"2012-10-16T09:58:54Z\",\"background\":false,\"estimated_speed\":17.076057434082,\"journey_id\":null,\"longitude\":-112.85077}},{\"waypoint\":{\"latitude\":37.3651116666667,\"timestamp\":\"2012-10-16T09:58:57Z\",\"background\":false,\"estimated_speed\":15.4269437789917,\"journey_id\":null,\"longitude\":-112.850766666667}},{\"waypoint\":{\"latitude\":37.36547,\"timestamp\":\"2012-10-16T09:59:02Z\",\"background\":false,\"estimated_speed\":17.1007328033447,\"journey_id\":null,\"longitude\":-112.85072}},{\"waypoint\":{\"latitude\":37.3658433333333,\"timestamp\":\"2012-10-16T09:59:11Z\",\"background\":false,\"estimated_speed\":10.3052024841309,\"journey_id\":null,\"longitude\":-112.850738333333}}]"
I get this data from a web service. But I see journey_id as null, whereas I want it to be 3, as I want to make the entry against this id.
How can I save this data in a child table using this id?
Your JSON string isn't opened correctly in your sample, as it's missing the leading '"'. Fixing that and moving on, here's what the JSON looks like "prettified":
[
{
"waypoint": {
"latitude": 37.3645616666667,
"timestamp": "2012-10-16T09:58:50Z",
"background": false,
"estimated_speed": 17.4189262390137,
"journey_id": null,
"longitude": -112.850676666667
}
},
{
"waypoint": {
"latitude": 37.3648733333333,
"timestamp": "2012-10-16T09:58:54Z",
"background": false,
"estimated_speed": 17.076057434082,
"journey_id": null,
"longitude": -112.85077
}
},
{
"waypoint": {
"latitude": 37.3651116666667,
"timestamp": "2012-10-16T09:58:57Z",
"background": false,
"estimated_speed": 15.4269437789917,
"journey_id": null,
"longitude": -112.850766666667
}
},
{
"waypoint": {
"latitude": 37.36547,
"timestamp": "2012-10-16T09:59:02Z",
"background": false,
"estimated_speed": 17.1007328033447,
"journey_id": null,
"longitude": -112.85072
}
},
{
"waypoint": {
"latitude": 37.3658433333333,
"timestamp": "2012-10-16T09:59:11Z",
"background": false,
"estimated_speed": 10.3052024841309,
"journey_id": null,
"longitude": -112.850738333333
}
}
]
You have an array of waypoint objects. Parsing that JSON into a Ruby object:
obj = JSON["[{\"waypoint\":..."] # purposely truncated for brevity
returns an array of hashes:
[{"waypoint"=>
{"latitude"=>37.3645616666667,
"timestamp"=>"2012-10-16T09:58:50Z",
"background"=>false,
"estimated_speed"=>17.4189262390137,
"journey_id"=>nil,
"longitude"=>-112.850676666667}},
{"waypoint"=>
{"latitude"=>37.3648733333333,
"timestamp"=>"2012-10-16T09:58:54Z",
"background"=>false,
"estimated_speed"=>17.076057434082,
"journey_id"=>nil,
"longitude"=>-112.85077}},
{"waypoint"=>
{"latitude"=>37.3651116666667,
"timestamp"=>"2012-10-16T09:58:57Z",
"background"=>false,
"estimated_speed"=>15.4269437789917,
"journey_id"=>nil,
"longitude"=>-112.850766666667}},
{"waypoint"=>
{"latitude"=>37.36547,
"timestamp"=>"2012-10-16T09:59:02Z",
"background"=>false,
"estimated_speed"=>17.1007328033447,
"journey_id"=>nil,
"longitude"=>-112.85072}},
{"waypoint"=>
{"latitude"=>37.3658433333333,
"timestamp"=>"2012-10-16T09:59:11Z",
"background"=>false,
"estimated_speed"=>10.3052024841309,
"journey_id"=>nil,
"longitude"=>-112.850738333333}}]
You can walk through that array and access, or change, the value for journey_id:
row = 3
obj = obj.map{ |h| h['waypoint']['journey_id'] = row }
obj.first
Looking at the first hash shows the value was changed, as were all the rest:
{
"waypoint" => {
"latitude" => 37.3645616666667,
"timestamp" => "2012-10-16T09:58:50Z",
"background" => false,
"estimated_speed" => 17.4189262390137,
"journey_id" => 3,
"longitude" => -112.850676666667
}
}
At that point, you need to recreate the JSON string. You can figure that out by reading the JSON documentation.
You could do all this by modifying the received string directly, but you don't want to get into the habit of directly modifying JSON strings because you can inadvertently damage the payload. It's better to let the parser give you the structure, modify that, then let JSON recreate the string.
How you store it to your database is left as an exercise for you also.

Resources