I have html look likes :
<div in-prop in-alias="" in-type="teacher"><div in-name="Alice">Hello <i in-name="Wonderland">World</i></div></div>
I want to collect custom attribute names based on prefix in- attribute name, I have working code and returns ["prop", "alias", "type", "name", "name"]
require 'nokogiri'
class PartB
def get_scheme(html)
get_doc(html).map { |elm|
elm.attributes.map{|k, v| k.gsub("in-", "") if !k.nil? && k.include?("in-")}
}.flatten.compact
end
def get_doc(html)
Nokogiri::HTML(html).search('*')
end
end
but I think that is redundant, because I use search('*') and it will get all html tags. Is there any way to do that?
doc = Nokogiri::HTML('<div in-prop in-alias="" in-type="teacher"><div in-name="Alice">Hello <i in-name="Wonderland">World</i></div></div>')
a = doc.xpath("//#*[starts-with(name(), 'in')]")
a.map { |i| i.name[3..-1] } # => ["prop", "alias", "type", "name", "name"]
Related
In ruby how can i parse a json to an array of objects?
Example: i have 2 classes:
class Person
attr_accessor :name, :address, :email, :address
end
And:
class Address
attr_accessor :street, :city, :state, :person
end
When i make a request i get the following json:
{
"data": [
{
"id": 9111316,
"name": "Mason Lee",
"email": "normanodonnell#biospan.com",
"address": {
"state": "American Samoa",
"street": "Cameron Court",
"city": "Wakulla"
}
},
{
"id": 500019,
"name": "Stella Weeks",
"email": "hansenwhitfield#candecor.com",
"address": {
"state": "Nevada",
"street": "Lake Street",
"city": "Wacissa"
}
}
]
}
This json should be parsed into an array of Person.
For now i'm doing:
#json gem
require 'json'
#...
#parse the json and get the 'data'
parsed_json = JSON.parse json
json_data = parsed_json['data']
objects = Array.new
if json_data.kind_of?(Array)
#add each person
json_data.each { |data|
current_person = Person.new
data.each { |k, v|
current_person.send("#{k}=", v)
}
objects.push(current_person)
}
end
#return the array of Person
objects
I have a lot of objects like the above example and do this parse manually is not desirable. There is an automated way to do this?
By "automated way" i mean something like in java with jackson:
ObjectMapper mapper = new ObjectMapper();
List<Person> myObjects = mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, Person.class));
You can initialize the Person with the hash:
json_data = JSON.parse(json)['data']
json_data.map do |data|
Person.new data
end
class Person
attr_accessor :name, :email, :address
def initialize params
params.each { |k,v| klass.public_send("#{k}=",v) }
end
end
If you want to choose the class dynamically, you can use:
json_data.map do |data|
klass = 'Person'
klass.get_const.new data
Why not just make the method yourself? Example:
require 'json'
def parse_json_to_class_array(data,root_node,to_klass)
json_data = JSON.parse(data)[root_node]
if json_data.is_a?(Array)
objects = json_data.map do |item|
klass = to_klass.new
item.each { |k,v| klass.public_send("#{k}=",v) }
klass
end
end
objects ||= []
end
Then for your example you could call it like so
json ="{\"data\":[
{\"id\":9111316,
\"name\":\"Mason Lee\",
\"email\":\"normanodonnell#biospan.com\",
\"address\":{
\"state\":\"American Samoa\",
\"street\":\"Cameron Court\",
\"city\":\"Wakulla\"
}
},
{\"id\":500019,
\"name\":\"Stella Weeks\",
\"email\":\"hansenwhitfield#candecor.com\",
\"address\":{
\"state\":\"Nevada\",
\"street\":\"Lake Street\",
\"city\":\"Wacissa\"
}
}
]
}"
class Person
attr_accessor :id, :name,:email, :address
end
parse_json_to_class_array(json,'data',Person)
#=>[#<Person:0x2ede818 #id=9111316, #name="Mason Lee", #email="normanodonnell#biospan.com", #address={"state"=>"American Samoa", "street"=>"Cameron Court", "city"=>"Wakulla"}>,
#<Person:0x2ede7a0 #id=500019, #name="Stella Weeks", #email="hansenwhitfield#candecor.com", #address={"state"=>"Nevada", "street"=>"Lake Street", "city"=>"Wacissa"}>]
Obviously you can expand this implementation to support single objects as well as overwrite Person#address= to perform the same operation and turn the address Hash into an Address object as well but this was not shown in your example so I did not take it this far in my answer.
A more dynamic example can be found Here
I'm new to Dashing (and relatively new to Ruby) so I apologize in advance if this is a dumb question. Basically I'm trying to get Dashing to read a json file that I update every 10 seconds. But I can't seem to get my job to read the file or post it out to my List widget.
Here's my job code:
require 'rubygems'
require 'json'
require 'pp'
name_list = Hash.new(0)
SCHEDULER.every '10s' do
json = File.read('list.json')
response = JSON.parse(json)
name_list[people] = {label: response.keys, value: response.keys[]}
send_event('whosHere', { items: response.values })
end
and my JSON:
{
"Mike": "Here",
"Jon": "Out",
}
And in case you need it my dashboard code:
<% content_for(:title) { "My super sweet dashboard" } %>
<div class="gridster">
<ul>
<li data-row="1" data-col="1" data-sizex="1" data-sizey="1">
<div data-id="whosHere" data-view="List" data-title="Who's Home" style="background-color:#96bf48;"></div>
</li>
</ul>
</div>
I see two issues:
1) You're sending the wrong object:
response = JSON.parse(json)
name_list[people] = {label: response.keys, value: response.keys[]}
send_event('whosHere', { items: response.values })
name_list[people] is your assumed result from processing your JSON, but you're sending response.values instead.
response is your parsed-JSON object returned. You must iterate over response to build an array of JSON objects to send to your List:
# new empty array of persons each time job is run
persons = []
# since your JSON file is single JSON object, using names for keys
person_names = response.keys
# for each name, do this
person_names.each do |name|
# use the 'name' key to get the value (their status)
person_status = response[name]
# create new Hash/JSON object for each person
# use "label" and "value" keys for Dashing List widget
person = Hash.new("label",name,"value",person_status)
# add this new person object to your persons array
persons.push(person)
end
Updated Job, which sends the persons array to Dashing instead:
require 'rubygems'
require 'json'
require 'pp'
name_list = Hash.new(0)
SCHEDULER.every '10s' do
json = File.read('list.json')
response = JSON.parse(json)
persons = []
person_names = response.keys
person_names.each do |name|
person_status = response[name]
person = Hash.new("label", name, "value", person_status)
persons.push(person)
end
send_event('whosHere', { items: persons })
end
2) Your JSON isn't formatted properly. Dashing's List widget is listening for an array of objects with "label" and "value" keys. Each person would be an individual JSON object, the person's name would be your "label" value, and their status would be their "value" value.
Here is the list-item HTML from the List widget:
<li data-foreach-item="items">
<span class="label" data-bind="item.label"></span>
<span class="value" data-bind="item.value"></span>
</li>
Each list item will display a single JSON object:
{
"label":"Mike",
"value":"Here"
}
So to display Mike and Jon's statuses in your list, you will need an array containing two JSON objects:
[
{
"label": "Mike",
"value": "Here"
},
{
"label": "Jon",
"value": "Out"
}
]
This array will be the "value" for your "items" key in your data object being sent to your List widget:
{
"items": [
{
"label": "Mike",
"value": "Here"
},
{
"label": "Jon",
"value": "Out"
}
]
}
I looked into different resources and still get confused on how to parse a json format to a custom object, for example
class Resident
attr_accessor :phone, :addr
def initialize(phone, addr)
#phone = phone
#addr = addr
end
end
and JSON file
{
"Resident": [
{
"phone": "12345",
"addr": "xxxxx"
}, {
"phone": "12345",
"addr": "xxxxx"
}, {
"phone": "12345",
"addr": "xxxxx"
}
]
}
what's the correct way to parse the json file into a array of 3 Resident object?
Today i was looking for something that converts json to an object, and this works like a charm:
person = JSON.parse(json_string, object_class: OpenStruct)
This way you could do person.education.school or person[0].education.school if the response is an array
I'm leaving it here because might be useful for someone
The following code is more simple:
require 'json'
data = JSON.parse(json_data)
residents = data['Resident'].map { |rd| Resident.new(rd['phone'], rd['addr']) }
If you're using ActiveModel::Serializers::JSON you can just call from_json(json) and your object will be mapped with those values.
class Person
include ActiveModel::Serializers::JSON
attr_accessor :name, :age, :awesome
def attributes=(hash)
hash.each do |key, value|
send("#{key}=", value)
end
end
def attributes
instance_values
end
end
json = {name: 'bob', age: 22, awesome: true}.to_json
person = Person.new
person.from_json(json) # => #<Person:0x007fec5e7a0088 #age=22, #awesome=true, #name="bob">
person.name # => "bob"
person.age # => 22
person.awesome # => true
require 'json'
class Resident
attr_accessor :phone, :addr
def initialize(phone, addr)
#phone = phone
#addr = addr
end
end
s = '{"Resident":[{"phone":"12345","addr":"xxxxx"},{"phone":"12345","addr":"xxxxx"},{"phone":"12345","addr":"xxxxx"}]}'
j = JSON.parse(s)
objects = j['Resident'].inject([]) { |o,d| o << Resident.new( d['phone'], d['addr'] ) }
p objects[0].phone
"12345"
We recently released a Ruby library static_struct that solves the issue. Check it out.
I'm writing a request test with RSpec and Capybara. I have a hash that maps form field names to expected values.
How can I check easily that each form field has the expected value?
So far, I'm doing this, but it's complex and unmaintainable. I'm also considering only two kind of input controls in this case (select boxes and the rest):
expected_data = {
"address" => "Fake st 123",
"city" => "Somewhere",
"email" => "whoknows#example.com",
"gender" => "Male",
"state" => "FL",
}
select_boxes = ["gender", "state"]
# check for the select boxes
expected_data.select {|k,v| select_boxes.include?(k)}.each do |name, expected_value|
page.has_select?(name, :selected_value => expected_value).should == true
end
# check for the input fields
expected_data.reject {|k,v| select_boxes.include?(k)}.values.each do |expected_value|
page.should have_css("input[value=\"#{expected_value}\"]")
end
Is there a gem or something to do this in one line?
I find the following far more maintainable:
describe "form" do
subject {page}
before { visit "/path/to/form" }
it { should have_field("address", :with => "Fake st 123") }
it { should have_select("gender", :selected => "Male") }
# And so on ...
end
I've been looking around for libraries that will allow me to get a multidimensional hash of a given XHTML string.
XHTML:
<div class="class-1 class-2" id="my-id">
<div class="classy">
</div>
</div>
Expected Hash:
hash = {
:div => {
:class => ['class-1', 'class-2'],
:id => ['my-id'],
:children => {
:div => {
:class => ['classy']
}
}
}
}
Your example does not really give a well defined definition of what should be returned. Are text nodes ignored? What happens if an element has multiple <div> child elements? What happens if the outer <div> element has an attribute named children?
In addition to that, you probably shouldn't build a structure like this if you have a way of using the built-in data structure of the XML/HTML parsing library of your choice, and using XPath queries to arrive at the data nodes you want.
Disregarding all of the above, here is a simple start that may come close to what you have in mind.
require "nokogiri"
class Nokogiri::XML::Node
def to_hash
# Build hash of attributes. Attribute values are split into arrays.
contents = Hash[attributes.collect { |name, value|
[name.to_sym, value.to_s.split(/\s+/)] }]
# Add array of child hashes recursively.
if element_children.any?
contents[:children] = element_children.collect { |child| child.to_hash }
end
# Return new hash with the element name as single key.
{ name.to_sym => contents }
end
end
Use as follows:
doc = Nokogiri::XML('<div class="class-1 class-2" id="my-id">
<div class="classy">
</div>
</div>')
doc.root.to_hash
#=> { :div =>
# { :class => ["class-1", "class-2"],
# :children =>
# [ { :div =>
# { :class => ["classy"] }
# } ],
# :id => ["my-id"]
# }
# }