json2 0.1.0
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.
- checksums.yaml +7 -0
- data/.coco.yml +3 -0
- data/.gitignore +15 -0
- data/.reek +7 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +154 -0
- data/Rakefile +7 -0
- data/bin/json2 +15 -0
- data/json2.gemspec +28 -0
- data/lib/json2.rb +14 -0
- data/lib/json2/body.rb +53 -0
- data/lib/json2/csv_with_header.rb +86 -0
- data/lib/json2/csv_without_header.rb +70 -0
- data/lib/json2/header.rb +36 -0
- data/lib/json2/option.rb +75 -0
- data/lib/json2/symbol_respond_to.rb +26 -0
- data/lib/json2/version.rb +3 -0
- data/spec/body_spec.rb +22 -0
- data/spec/csv_with_header_spec.rb +43 -0
- data/spec/csv_without_header_spec.rb +22 -0
- data/spec/data/README +4 -0
- data/spec/data/colors-array.json +30 -0
- data/spec/data/colors-object.json +33 -0
- data/spec/data/colors2.json +12 -0
- data/spec/data/colors3.json +9 -0
- data/spec/data/github-user.json +43 -0
- data/spec/data/google-maps-example.json +32 -0
- data/spec/data/product2.json +32 -0
- data/spec/data/products.json +26 -0
- data/spec/data/users.json +1 -0
- data/spec/data/vote-loi-renseignement.json +5 -0
- data/spec/data/votes.json +15 -0
- data/spec/header_spec.rb +18 -0
- data/spec/integration/colors_array_spec.rb +25 -0
- data/spec/integration/colors_object_spec.rb +24 -0
- data/spec/integration/path_selection_spec.rb +45 -0
- data/spec/integration/votes_spec.rb +21 -0
- data/spec/json2_spec.rb +7 -0
- data/spec/option_spec.rb +67 -0
- data/spec/spec_helper.rb +9 -0
- metadata +197 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 51f0cb006cd0e9d7641a2beb85a77ff46258f693
|
4
|
+
data.tar.gz: 84d96f803db004b7b12b2eb313bd4fca9de1fb9f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7317aa9c145dd7fcc928e5223cc26bcdf3b9f66ed7d3d2076f44336a54806b437c38a3fa2360185132c01b2cff11ce06b46a76ce482bc8e768e8d0be3426cda1
|
7
|
+
data.tar.gz: 0f5918f0343a5b281ea5bac2240a6194a201ee725162040f86fa42e7a02555f05303fc7033d12c81e765a8af20e49ec1ae48ef5debd041c68dd32b8a79999571
|
data/.coco.yml
ADDED
data/.gitignore
ADDED
data/.reek
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Change Log
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
This project adheres to [Semantic Versioning](http://semver.org/).
|
4
|
+
|
5
|
+
## [Unreleased] - unreleased
|
6
|
+
|
7
|
+
## [0.1.0] - 2015-07-10
|
8
|
+
### Added
|
9
|
+
- Basic Json to Csv behavior, based on several Json examples.
|
10
|
+
- Works well with other command line tools by using a file as input or by
|
11
|
+
listening to STDIN.
|
12
|
+
- Option `--without-header` to parse the Json when it doesn't seems to have
|
13
|
+
*heading information* inside itself.
|
14
|
+
- Option `--path` to extract a selection of the Json file.
|
15
|
+
- Common switches `--version` and `--help`.
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 lkdjiin
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
# Json2
|
2
|
+
|
3
|
+
Json2 transforms a Json file into a Csv one.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'json2'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install json2
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Example 1
|
24
|
+
|
25
|
+
Given the following Json file:
|
26
|
+
|
27
|
+
```json
|
28
|
+
[
|
29
|
+
{
|
30
|
+
"color": "red",
|
31
|
+
"value": "#f00"
|
32
|
+
},
|
33
|
+
{
|
34
|
+
"color": "green",
|
35
|
+
"value": "#0f0"
|
36
|
+
},
|
37
|
+
{
|
38
|
+
"color": "blue",
|
39
|
+
"value": "#00f"
|
40
|
+
}
|
41
|
+
]
|
42
|
+
```
|
43
|
+
|
44
|
+
You could obtain a Csv with a header like that:
|
45
|
+
|
46
|
+
$ json2 colors-array.json
|
47
|
+
color,value
|
48
|
+
red,#f00
|
49
|
+
green,#0f0
|
50
|
+
blue,#00f
|
51
|
+
|
52
|
+
### Example 2
|
53
|
+
|
54
|
+
With this simpler kind of Json:
|
55
|
+
|
56
|
+
```json
|
57
|
+
{
|
58
|
+
"red":"#f00",
|
59
|
+
"green":"#0f0",
|
60
|
+
"blue":"#00f"
|
61
|
+
}
|
62
|
+
```
|
63
|
+
|
64
|
+
Here is the resulting Csv:
|
65
|
+
|
66
|
+
|
67
|
+
$ json2 colors3.json
|
68
|
+
red,green,blue
|
69
|
+
#f00,#0f0,#00f
|
70
|
+
|
71
|
+
### Example 3
|
72
|
+
|
73
|
+
Take a slightly more complicated Json file:
|
74
|
+
|
75
|
+
```json
|
76
|
+
{
|
77
|
+
"id": "0001",
|
78
|
+
"type": "donut",
|
79
|
+
"batters":
|
80
|
+
{
|
81
|
+
"batter":
|
82
|
+
[
|
83
|
+
{ "id": "1001", "type": "Regular" },
|
84
|
+
{ "id": "1002", "type": "Chocolate" },
|
85
|
+
{ "id": "1003", "type": "Blueberry" },
|
86
|
+
{ "id": "1004", "type": "Devil's Food" }
|
87
|
+
]
|
88
|
+
},
|
89
|
+
"topping":
|
90
|
+
[
|
91
|
+
{ "id": "5001", "type": "None" },
|
92
|
+
{ "id": "5002", "type": "Glazed" },
|
93
|
+
{ "id": "5005", "type": "Sugar" }
|
94
|
+
]
|
95
|
+
}
|
96
|
+
```
|
97
|
+
|
98
|
+
Say you want to extract the *batter* stuff. Use the `--path` switch:
|
99
|
+
|
100
|
+
$ json2 --path='batters.batter' products.json
|
101
|
+
id,type
|
102
|
+
1001,Regular
|
103
|
+
1002,Chocolate
|
104
|
+
1003,Blueberry
|
105
|
+
1004,Devil's Food
|
106
|
+
|
107
|
+
### Example 4
|
108
|
+
|
109
|
+
Json don't always map very well with Csv.
|
110
|
+
Sometimes there is simply no header information inside the Json:
|
111
|
+
|
112
|
+
```json
|
113
|
+
{
|
114
|
+
"Nom du parti": {
|
115
|
+
"Abstention": [
|
116
|
+
"Jean"
|
117
|
+
],
|
118
|
+
"Non-votant": [],
|
119
|
+
"Contre": [
|
120
|
+
"Alice",
|
121
|
+
"Georges"
|
122
|
+
],
|
123
|
+
"Pour": [
|
124
|
+
"Julie"
|
125
|
+
]
|
126
|
+
}
|
127
|
+
}
|
128
|
+
```
|
129
|
+
|
130
|
+
In this case, you can use the `--without-header` switch:
|
131
|
+
|
132
|
+
$ json2 --without-header votes.json
|
133
|
+
Nom du parti,Abstention,Jean
|
134
|
+
Nom du parti,Non-votant,
|
135
|
+
Nom du parti,Contre,Alice
|
136
|
+
Nom du parti,Contre,Georges
|
137
|
+
Nom du parti,Pour,Julie
|
138
|
+
|
139
|
+
## Contributing
|
140
|
+
|
141
|
+
1. Fork it ( https://github.com/[my-github-username]/json2/fork )
|
142
|
+
2. **Create your feature branch** (`git checkout -b my-new-feature`)
|
143
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
144
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
145
|
+
5. Create a new Pull Request
|
146
|
+
|
147
|
+
## License
|
148
|
+
|
149
|
+
MIT
|
150
|
+
|
151
|
+
## Questions and/or Comments
|
152
|
+
|
153
|
+
Feel free to email [Xavier Nayrac](mailto:xavier.nayrac@gmail.com)
|
154
|
+
with any questions, or contact me on [twitter](https://twitter.com/lkdjiin).
|
data/Rakefile
ADDED
data/bin/json2
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'json2'
|
4
|
+
|
5
|
+
opt = Json2::Option.new
|
6
|
+
|
7
|
+
input = JSON.parse((ARGV[0] ? File.open(ARGV[0]) : $stdin).read)
|
8
|
+
|
9
|
+
json = if opt[:without_header]
|
10
|
+
Json2::CsvWithoutHeader.new(input)
|
11
|
+
else
|
12
|
+
Json2::CsvWithHeader.new(input, opt)
|
13
|
+
end
|
14
|
+
|
15
|
+
print json.output
|
data/json2.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'json2/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "json2"
|
8
|
+
spec.version = Json2::VERSION
|
9
|
+
spec.authors = ["lkdjiin"]
|
10
|
+
spec.email = ["xavier.nayrac@gmail.com"]
|
11
|
+
spec.summary = %q{Transform json to csv.}
|
12
|
+
spec.description = %q{json2 takes a json file as input and outputs a clean
|
13
|
+
csv.}
|
14
|
+
spec.homepage = "https://github.com/lkdjiin/json2"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0")
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
spec.add_development_dependency "reek", "~> 3.0"
|
26
|
+
spec.add_development_dependency "coco", "~> 0.13.0"
|
27
|
+
spec.add_development_dependency "flay"
|
28
|
+
end
|
data/lib/json2.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
require "json2/symbol_respond_to"
|
5
|
+
require "json2/version"
|
6
|
+
require "json2/csv_with_header"
|
7
|
+
require "json2/csv_without_header"
|
8
|
+
require "json2/header"
|
9
|
+
require "json2/body"
|
10
|
+
require "json2/option"
|
11
|
+
|
12
|
+
# Transform a Json file into Csv.
|
13
|
+
module Json2
|
14
|
+
end
|
data/lib/json2/body.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
module Json2
|
2
|
+
|
3
|
+
# Build a csv body.
|
4
|
+
class Body
|
5
|
+
|
6
|
+
# Get the body of a Csv file.
|
7
|
+
#
|
8
|
+
# For a description of the parameters see Body#initialize.
|
9
|
+
#
|
10
|
+
# Returns the String body, that is several lines in a single string,
|
11
|
+
# each lines with comma separated values.
|
12
|
+
def self.get(nodes, keys, column_size)
|
13
|
+
new(nodes, keys, column_size).get
|
14
|
+
end
|
15
|
+
|
16
|
+
# Creates a new Body instance.
|
17
|
+
#
|
18
|
+
# nodes - A Hash representing all columns. The key is the
|
19
|
+
# Symbol column's header and the value is an Array
|
20
|
+
# full of column's values.
|
21
|
+
# keys - An Array of Symbol header's column. Why passing the
|
22
|
+
# column's names while they are contained in `nodes`?
|
23
|
+
# This is because we want the header's names in that
|
24
|
+
# particular order given by `keys`, we can't rely on
|
25
|
+
# the order of the *Hash* `nodes`.
|
26
|
+
# column_size - The Fixnum size of a column. TODO I think this
|
27
|
+
# parameter isn't needed.
|
28
|
+
def initialize(nodes, keys, column_size)
|
29
|
+
@nodes = nodes
|
30
|
+
@column_size = column_size
|
31
|
+
@keys = keys
|
32
|
+
@body = ''
|
33
|
+
end
|
34
|
+
|
35
|
+
# Get the body of a Csv file. See also Body.get.
|
36
|
+
#
|
37
|
+
# Returns the String body.
|
38
|
+
def get
|
39
|
+
(0...@column_size).each {|observation| process_line(observation) }
|
40
|
+
@body
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def process_line(observation)
|
46
|
+
line = []
|
47
|
+
@keys.each {|key| line << @nodes[key][observation] }
|
48
|
+
@body += line.join(',') + "\n"
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Json2
|
2
|
+
|
3
|
+
# Turn a Json input into a Csv output with a header.
|
4
|
+
class CsvWithHeader
|
5
|
+
|
6
|
+
# Creates a new CsvWithHeader instance.
|
7
|
+
#
|
8
|
+
# input - A Hash representing a Json file. This is typically
|
9
|
+
# obtained with JSON.parse.
|
10
|
+
# options - Optional. A Hash of options, see Option to get the list.
|
11
|
+
def initialize(input, options = {})
|
12
|
+
@options = options
|
13
|
+
@input = input
|
14
|
+
@names_stack = []
|
15
|
+
@nodes = Hash.new {|hash, key| hash[key] = [] }
|
16
|
+
process_input
|
17
|
+
@column_size = @nodes.values.map {|node| node.size }.max
|
18
|
+
@keys = @nodes.keys.select {|key| @nodes[key].size == @column_size }
|
19
|
+
end
|
20
|
+
|
21
|
+
# Get the Csv.
|
22
|
+
#
|
23
|
+
# Returns the whole document as a single String.
|
24
|
+
def output
|
25
|
+
Header.get(@keys) + Body.get(@nodes, @keys, @column_size)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def process_input
|
31
|
+
if @input.respond_to?(:each_key)
|
32
|
+
process_keys(@input)
|
33
|
+
else
|
34
|
+
process_array(@input)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def process_keys(object)
|
39
|
+
if object.respond_to?(:each_key)
|
40
|
+
object.each_key do |key|
|
41
|
+
@names_stack.push(key)
|
42
|
+
process_key(object[key])
|
43
|
+
@names_stack.pop
|
44
|
+
end
|
45
|
+
else
|
46
|
+
error(98, 'Error, try with json2 --without-header')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def process_key(object)
|
51
|
+
case object
|
52
|
+
when ~:each_key then process_keys(object)
|
53
|
+
when ~:at then process_array(object)
|
54
|
+
else
|
55
|
+
@nodes[current_line] <<= object if line_eligible?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def process_array(object)
|
60
|
+
object.each {|element| process_keys(element) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def current_line
|
64
|
+
@names_stack.join('.')
|
65
|
+
end
|
66
|
+
|
67
|
+
def line_eligible?
|
68
|
+
with_a_good_path? || without_path?
|
69
|
+
end
|
70
|
+
|
71
|
+
def with_a_good_path?
|
72
|
+
@options[:with_path] && current_line.start_with?(@options[:path])
|
73
|
+
end
|
74
|
+
|
75
|
+
def without_path?
|
76
|
+
!@options[:with_path]
|
77
|
+
end
|
78
|
+
|
79
|
+
def error(code, message)
|
80
|
+
warn(message)
|
81
|
+
exit(code)
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|