motion-json-decoder 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +82 -0
- data/Rakefile +2 -0
- data/lib/motion-json-decoder.rb +15 -0
- data/lib/motion_json_decoder/date_parser.rb +14 -0
- data/lib/motion_json_decoder/node.rb +49 -0
- data/lib/motion_json_decoder/version.rb +5 -0
- data/motion-json-decoder.gemspec +17 -0
- data/spec/motion_json_decoder/node_spec.rb +47 -0
- metadata +70 -0
data/README.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
motion-json-decoder
|
2
|
+
===================
|
3
|
+
|
4
|
+
Turn JSON nodes into proper Ruby objects.
|
5
|
+
|
6
|
+
Installation
|
7
|
+
------------
|
8
|
+
* Add `motion-json-decoder` to your Gemfile and run `bundle`.
|
9
|
+
* Add `require 'motion-json-decoder` to your Rakefile.
|
10
|
+
|
11
|
+
Basic Example
|
12
|
+
-------------
|
13
|
+
|
14
|
+
Let's say you have a RubyMotion app which parses this response from the server:
|
15
|
+
|
16
|
+
{
|
17
|
+
"people":
|
18
|
+
[
|
19
|
+
{
|
20
|
+
"first_name": "John",
|
21
|
+
"last_name": "Smith",
|
22
|
+
"date_of_birth": "1980-01-03"
|
23
|
+
},
|
24
|
+
{
|
25
|
+
"first_name": "Joe",
|
26
|
+
"last_name": "Bloggs",
|
27
|
+
"date_of_birth": "1967-10-11"
|
28
|
+
}
|
29
|
+
]
|
30
|
+
}
|
31
|
+
|
32
|
+
So that's a *people* collection which contains two *person* nodes.
|
33
|
+
|
34
|
+
There may be a number of place in your app where you want to display a person's full name:
|
35
|
+
|
36
|
+
names = []
|
37
|
+
json['people'].each do |person|
|
38
|
+
full_name = person['first_name'] + ' ' + person['last_name']
|
39
|
+
names << full_name
|
40
|
+
end
|
41
|
+
|
42
|
+
But doing this in multiply places isn't DRY. You could write a helper method, but it's hard to think where
|
43
|
+
that should live.
|
44
|
+
|
45
|
+
*motion-json-decoder* allows the creating of a mapping between your JSON nodes and simple objects. Just include the module in your class:
|
46
|
+
|
47
|
+
class Person
|
48
|
+
include JSONDecoder::Node
|
49
|
+
|
50
|
+
field :first_name
|
51
|
+
field :last_name
|
52
|
+
|
53
|
+
def full_name
|
54
|
+
first_name + ' ' + last_name
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
You can then treat person as a proper object:
|
59
|
+
|
60
|
+
names = []
|
61
|
+
json['people'].each do |person_node|
|
62
|
+
person = Person.new person_node
|
63
|
+
names << person.full_name
|
64
|
+
end
|
65
|
+
|
66
|
+
Typo Catching
|
67
|
+
-------------
|
68
|
+
|
69
|
+
Under the hood, motion-json-decoder uses Hash#fetch rather than Hash#[], so if you call a field which doesn't exist, you'll get an exception right away, rather than a potentially difficult-to-debug nil return value.
|
70
|
+
|
71
|
+
Collections
|
72
|
+
------------
|
73
|
+
|
74
|
+
You can specify that a nodes contains a collection of other 'resources' rather than a simple literals:
|
75
|
+
|
76
|
+
collection :organisations, :class_name -> { Organisation }
|
77
|
+
|
78
|
+
class_name parameter should be another class which includes the `JSONDecoder::Node` module.
|
79
|
+
|
80
|
+
When you call json.people, rather than array of hashes, you'll get an array of Organisation objects.
|
81
|
+
|
82
|
+
The use of the lambda (->) is to avoid dependency resolution problems at compile time.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
unless ENV['SKIP_RM_CHECK']
|
2
|
+
unless defined?(Motion::Project::Config)
|
3
|
+
raise "This file must be required within a RubyMotion project Rakefile."
|
4
|
+
end
|
5
|
+
|
6
|
+
Motion::Project::App.setup do |app|
|
7
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'motion_json_decoder/*.rb')).each do |file|
|
8
|
+
app.files.unshift(file)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'motion_json_decoder/version'
|
14
|
+
require 'motion_json_decoder/node'
|
15
|
+
require 'motion_json_decoder/date_parser'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module JSONDecoder
|
2
|
+
class DateParser
|
3
|
+
def initialize(format = "yyyy-MM-dd")
|
4
|
+
@format = format
|
5
|
+
end
|
6
|
+
|
7
|
+
def parse(date_string)
|
8
|
+
date_formatter = NSDateFormatter.alloc.init
|
9
|
+
date_formatter.setDateFormat @format
|
10
|
+
result = date_formatter.dateFromString date_string
|
11
|
+
result or raise "Failed to parse date '#{date_string}' using format '#{format}'"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
module JSONDecoder
|
3
|
+
module Node
|
4
|
+
def initialize(json)
|
5
|
+
self.json = json
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.send :extend, ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def field(field_name, opts = {})
|
14
|
+
type = opts[:type]
|
15
|
+
klass = opts[:using]
|
16
|
+
raise "Cannot use :type and :using together" if type && klass
|
17
|
+
method_name = opts[:as] || field_name
|
18
|
+
define_method method_name do
|
19
|
+
result = json[field_name.to_s]
|
20
|
+
if type == :date
|
21
|
+
JSONDecoder::DateParser.new.parse(result) # TODO move this into lib if gemifying
|
22
|
+
elsif klass
|
23
|
+
klass.new result
|
24
|
+
else
|
25
|
+
result
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def collection(name, opts = {})
|
31
|
+
klass = opts.fetch(:class_name)
|
32
|
+
define_method name do
|
33
|
+
# Using a block to avoid dependency reolutions problems.
|
34
|
+
lambda do
|
35
|
+
json.fetch(name.to_s).map { |element| klass.call.new(element) }
|
36
|
+
end.call
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def links
|
42
|
+
json[:_links]
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_accessor :json
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/motion_json_decoder/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = "motion-json-decoder"
|
6
|
+
gem.authors = ["Andy Waite"]
|
7
|
+
gem.email = ["andy@andywaite.com"]
|
8
|
+
gem.description = "Turn JSON nodes into rich Ruby objects"
|
9
|
+
gem.summary = "JSON decoder for RubyMotion"
|
10
|
+
gem.homepage = "https://github.com/andyw8/motion-json-decoder"
|
11
|
+
|
12
|
+
gem.files = `git ls-files`.split($\)
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
gem.version = Motion::JSONDecoder::VERSION
|
16
|
+
gem.add_development_dependency 'rspec'
|
17
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'motion_json_decoder'
|
2
|
+
|
3
|
+
describe "JSONDecoder::Node" do
|
4
|
+
it "exposes fields" do
|
5
|
+
class Foo
|
6
|
+
include JSONDecoder::Node
|
7
|
+
field :first_name
|
8
|
+
end
|
9
|
+
|
10
|
+
json_hash = {'first_name' => 'Andy'}
|
11
|
+
node = Foo.new json_hash
|
12
|
+
node.first_name.should == 'Andy'
|
13
|
+
end
|
14
|
+
|
15
|
+
it "allows fields to be aliased" do
|
16
|
+
class Foo
|
17
|
+
include JSONDecoder::Node
|
18
|
+
field :first_name, as: 'forename'
|
19
|
+
end
|
20
|
+
|
21
|
+
json_hash = {'first_name' => 'Andy'}
|
22
|
+
node = Foo.new json_hash
|
23
|
+
node.forename.should == 'Andy'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "handles collections" do
|
27
|
+
class Organisation
|
28
|
+
include JSONDecoder::Node
|
29
|
+
field :name
|
30
|
+
end
|
31
|
+
|
32
|
+
class Foo
|
33
|
+
include JSONDecoder::Node
|
34
|
+
collection :organisations, class_name: -> { Organisation }
|
35
|
+
end
|
36
|
+
|
37
|
+
json_hash = {
|
38
|
+
'organisations' =>
|
39
|
+
[
|
40
|
+
{'name' => 'Reddit'},
|
41
|
+
{'name' => 'Hacker News'}
|
42
|
+
]
|
43
|
+
}
|
44
|
+
node = Foo.new json_hash
|
45
|
+
node.organisations.map(&:name).should == ['Reddit', 'Hacker News']
|
46
|
+
end
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: motion-json-decoder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Andy Waite
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: Turn JSON nodes into rich Ruby objects
|
31
|
+
email:
|
32
|
+
- andy@andywaite.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- README.md
|
38
|
+
- Rakefile
|
39
|
+
- lib/motion-json-decoder.rb
|
40
|
+
- lib/motion_json_decoder/date_parser.rb
|
41
|
+
- lib/motion_json_decoder/node.rb
|
42
|
+
- lib/motion_json_decoder/version.rb
|
43
|
+
- motion-json-decoder.gemspec
|
44
|
+
- spec/motion_json_decoder/node_spec.rb
|
45
|
+
homepage: https://github.com/andyw8/motion-json-decoder
|
46
|
+
licenses: []
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
requirements: []
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.8.23
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: JSON decoder for RubyMotion
|
69
|
+
test_files:
|
70
|
+
- spec/motion_json_decoder/node_spec.rb
|