diff --git a/.gitignore b/.gitignore index 173bd29c..10aef982 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .vscode/ build/ solutions/ +_build diff --git a/CMakeLists.txt b/CMakeLists.txt index a29df37e..82ab4008 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,3 +23,7 @@ file(GLOB tp_folders tp-*) foreach(tp ${tp_folders}) add_subdirectory(${tp}) endforeach() + + +# To uncomment to start the TP about JSON +enable_testing() diff --git a/tp-json/ArrayNode.cpp b/tp-json/ArrayNode.cpp new file mode 100644 index 00000000..3232bf18 --- /dev/null +++ b/tp-json/ArrayNode.cpp @@ -0,0 +1 @@ +#include "ArrayNode.hpp" \ No newline at end of file diff --git a/tp-json/ArrayNode.hpp b/tp-json/ArrayNode.hpp new file mode 100644 index 00000000..3fb88675 --- /dev/null +++ b/tp-json/ArrayNode.hpp @@ -0,0 +1,132 @@ +#pragma once + +#include "Node.hpp" + +#include +#include +#include + +class ArrayNode : public Node +{ +private: + std::vector _data; + +public: + std::vector const& data() const { return _data; } + + std::string print() const override + { + std::string result = "["; + for (unsigned i = 0; i < _data.size(); ++i) + { + if (i > 0) + result += ","; + result += _data[i]->print(); + } + result += ']'; + return result; + } + + void add(Node_ptr node) { _data.push_back(std::move(node)); } + + ArrayNode() + : Node(NodeKind::ARRAY) + {} + + ArrayNode(std::vector data) + : Node { NodeKind::ARRAY } + , _data { std::move(data) } + {} + + static inline std::unique_ptr make_ptr(std::vector data = {}) + { + return std::make_unique(std::move(data)); + } + + size_t child_count() const override { return _data.size(); } + + ArrayNode* as_ArrayNode() override { return this; } + const ArrayNode* as_ArrayNode() const override { return this; } + + inline bool operator==(const Node& other) const override + { + if (!(other.is_of_kind(kind()))) + { + std::cerr << kind() << "!=" << other.kind() << std::endl; + return false; + } + ArrayNode const* other_s = other.as_ArrayNode(); + size_t size = child_count(); + if (other_s->child_count() != size) + { + std::cerr << size << " != " << other_s->child_count() << std::endl; + return false; + } + for (unsigned i = 0; i < size; i++) + { + if (*(other_s->_data[i]) != *(_data[i])) + { + std::cerr << *(other_s->_data[i]) << std::endl; + std::cerr << *_data[i] << std::endl; + return false; + } + } + return true; + } + + size_t height() const override + { + return std::accumulate(_data.begin(), _data.end(), 0, + [](size_t i, Node_ptr const& child) + { return std::max(i, 1u + child->height()); }); + } + + virtual size_t node_count() const override + { + return std::accumulate(_data.begin(), _data.end(), 1, + [](size_t i, Node_ptr const& child) { return i + child->node_count(); }); + } + + std::vector::iterator begin() { return _data.begin(); } + std::vector::iterator end() { return _data.end(); } + + std::vector::const_iterator begin() const { return _data.begin(); } + std::vector::const_iterator end() const { return _data.end(); } + + Node_ptr deep_copy() const override + { + auto result = make_ptr(); + for (auto const& elt : this->data()) + result->add(elt->deep_copy()); + return result; + } + + std::string dot_label() const override { return "[...]"; } + + virtual void dot(std::ostream& o) const + { + Node::dot(o); + o << "subgraph cluster_" << dot_id() << " {style=invis;" << std::endl; + for (auto it = _data.rbegin(); it != _data.rend(); ++it) + { + (*it)->dot(o); + o << dot_id() << " -> " << (*it)->dot_id() << " ;" << std::endl; + } + o << "}" << std::endl; + } + + Node* at(size_t index) override + { + if (index < child_count()) + return _data.at(index).get(); + else + return nullptr; + } + const Node* at(size_t index) const override + { + if (index < child_count()) + return _data.at(index).get(); + else + return nullptr; + } +}; diff --git a/tp-json/BooleanLeaf.cpp b/tp-json/BooleanLeaf.cpp new file mode 100644 index 00000000..530e217c --- /dev/null +++ b/tp-json/BooleanLeaf.cpp @@ -0,0 +1 @@ +#include "BooleanLeaf.hpp" \ No newline at end of file diff --git a/tp-json/BooleanLeaf.hpp b/tp-json/BooleanLeaf.hpp new file mode 100644 index 00000000..a09bbe63 --- /dev/null +++ b/tp-json/BooleanLeaf.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "Node.hpp" + +class BooleanLeaf : public Node +{ +private: + bool _data; + +public: + std::string print() const override { return _data ? "true" : "false"; } + BooleanLeaf(bool data) + : Node { NodeKind::BOOLEAN } + , _data { data } + {} + + static std::unique_ptr make_ptr(bool data) { return std::make_unique(data); } + + BooleanLeaf* as_BooleanLeaf() override { return this; } + BooleanLeaf const* as_BooleanLeaf() const override { return this; } + + inline bool operator==(const Node& other) const override + { + if (!(other.is_of_kind(kind()))) + { + return false; + } + return (_data == other.as_BooleanLeaf()->_data); + } + + const bool& data() const { return _data; } + + Node_ptr deep_copy() const override { return make_ptr(data()); } + + std::string dot_label() const override { return _data ? "true" : "false"; } +}; diff --git a/tp-json/CMakeLists.txt b/tp-json/CMakeLists.txt new file mode 100644 index 00000000..dda6a6a2 --- /dev/null +++ b/tp-json/CMakeLists.txt @@ -0,0 +1,3 @@ +file(GLOB TP-JSON-SOURCES *.cpp) + +add_subdirectory(tests) diff --git a/tp-json/JsonParser.cpp b/tp-json/JsonParser.cpp new file mode 100644 index 00000000..a3847093 --- /dev/null +++ b/tp-json/JsonParser.cpp @@ -0,0 +1,264 @@ +#include "JsonParser.hpp" + +#include +#include +#include +#include +#include +#include + +void JsonParser::extract_spaces() +{ + while (!_in.eof() && std::isspace(_in.peek())) + _in.get(); +} + +bool JsonParser::check_next_char_equals(char expected_char, std::string_view alternatives) +{ + int expected_char_as_int = expected_char; + int next_char = _in.peek(); + if (expected_char_as_int == next_char) + { + _in.get(); + return true; + } + std::cerr << "Unexpected character ("; + if (std::isprint(next_char)) + { + std::cerr << static_cast(next_char); + } + else if (next_char == std::char_traits::eof()) + { + std::cerr << "EOF"; + } + else + { + std::cerr << "code: " << next_char; + } + + if (alternatives != "") + { + std::cerr << "). Expecting a char in \"" << alternatives << "\"." << std::endl; + } + else + { + std::cerr << "). Expecting '" << expected_char << "'." << std::endl; + } + return false; +} + +Node_ptr JsonParser::parse_Node() +{ + extract_spaces(); + const int c = _in.peek(); + switch (c) + { + case '{': + return parse_ObjectNode(); + case '[': + return parse_ArrayNode(); + case '"': + return parse_StringLeaf(); + case 'f': + return parse_constant("false"); + case 't': + return parse_constant("true"); + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'e': + case 'E': + case '.': + case '-': + case '+': + return parse_NumberLeaf(); + default: + check_next_char_equals('{', "{[\"ft0123456789eE.-+"); + return nullptr; + } +} + +Node_ptr JsonParser::parse_constant(std::string_view target) +{ + assert(target == "true" || target == "false"); + std::string actual_string; + actual_string.reserve(target.size()); + for (char expected_char : target) + { + char actual_char; + _in >> actual_char; + actual_string += actual_char; + if (actual_char != expected_char) + { + std::cerr << "Expecting JSON value (probably constant " << target << ") at position " + << (_in.tellg() - (long)actual_string.size()) << ". Got string starting with " + << actual_string << std::endl; + return nullptr; + } + } + if (target == "true") + return BooleanLeaf::make_ptr(true); + if (target == "false") + return BooleanLeaf::make_ptr(false); + return nullptr; +} + +std::optional JsonParser::extract_string() +{ + extract_spaces(); + check_next_char_equals('"'); + std::string s; + char c = '\0'; + while (((c = _in.get()) != '"') && !_in.eof()) + { + if (c == '\\') /* If c is a '\' then the next char is escaped. */ + { + c = _in.get(); + if (_in.eof()) + break; + } + s += c; + } + + if (_in.eof()) + { + std::cerr << "Found EOF while looking for closing \"." << std::endl; + return std::optional(); + } + return s; +} + +Node_ptr JsonParser::parse_StringLeaf() +{ + auto str = extract_string(); + if (str) + return StringLeaf::make_ptr(std::move(str.value())); + else + return nullptr; +} + +Node_ptr JsonParser::parse_NumberLeaf() +{ + unsigned starting_pos = _in.tellg(); + + double d; + _in >> d; + + size_t end_pos = _in.tellg(); + + if (starting_pos == end_pos) + return nullptr; + else + return NumberLeaf::make_ptr(d); +} + +Node_ptr JsonParser::parse_ArrayNode() +{ + if (!check_next_char_equals('[')) + return nullptr; + auto arrayNode = ArrayNode::make_ptr(); + extract_spaces(); + if (_in.peek() == ']') + { + _in.get(); + return arrayNode; + } + else + { + do + { + auto child = parse_Node(); + if (child == nullptr) + return nullptr; + else + arrayNode->add(std::move(child)); + extract_spaces(); + } + while (_in.get() == ','); + + _in.unget(); + if (check_next_char_equals(']', ",]")) + return arrayNode; + else + return nullptr; + } +} + +Node_ptr JsonParser::parse_ObjectNode() +{ + if (!check_next_char_equals('{')) + return nullptr; + auto objectNode = ObjectNode::make_ptr(); + extract_spaces(); + if (_in.peek() == '}') + { + _in.get(); + return objectNode; + } + + do + { + std::optional opt_key = extract_string(); + if (!opt_key) + return nullptr; + extract_spaces(); + if (!check_next_char_equals(':')) + return nullptr; + auto child = parse_Node(); + if (child == nullptr) + return nullptr; + else + objectNode->add(std::move(opt_key.value()), std::move(child)); + extract_spaces(); + } + while (_in.get() == ','); + _in.unget(); + if (check_next_char_equals('}', ",}")) + return objectNode; + else + return nullptr; +} + +Node_ptr JsonParser::run() +{ + return parse_Node(); +} + +Node_ptr JsonParser::parse_from_istream(std::istream& in) +{ + JsonParser parser(in); + Node_ptr parsed_tree = parser.run(); + if (parsed_tree) + { + parser.extract_spaces(); + if (in.good() && (in.get() != std::char_traits::eof())) + std::cerr << "[Warning] Ended parsing before reaching EOF." << std::endl; + return parsed_tree; + } + else + exit(EXIT_FAILURE); +} + +Node_ptr JsonParser::parse_from_file(std::string const& path) +{ + std::ifstream in(path.c_str(), std::ifstream::in); + if (!in.is_open()) + { + std::cerr << "Could not open file: \"" << path << '"' << std::endl; + exit(EXIT_FAILURE); + } + return parse_from_istream(in); +} + +Node_ptr JsonParser::parse_from_string(std::string const& str) +{ + std::stringstream ss { str }; + return parse_from_istream(ss); +} diff --git a/tp-json/JsonParser.hpp b/tp-json/JsonParser.hpp new file mode 100644 index 00000000..cd3b4b72 --- /dev/null +++ b/tp-json/JsonParser.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "ArrayNode.hpp" +#include "BooleanLeaf.hpp" +#include "Node.hpp" +#include "NodeKind.hpp" +#include "NumberLeaf.hpp" +#include "ObjectNode.hpp" +#include "StringLeaf.hpp" + +#include +#include + +class JsonParser +{ +private: + std::istream& _in; + + void extract_spaces(); + bool check_next_char_equals(char expected_char, std::string_view alternatives = ""); + + std::optional extract_string(); + + Node_ptr parse_Node(); + Node_ptr parse_constant(std::string_view target); + Node_ptr parse_StringLeaf(); + Node_ptr parse_NumberLeaf(); + Node_ptr parse_ArrayNode(); + Node_ptr parse_ObjectNode(); + +public: + JsonParser(std::istream& in) + : _in(in) + {} + + Node_ptr run(); + + static Node_ptr parse_from_istream(std::istream& in); + static Node_ptr parse_from_file(std::string const& path); + static Node_ptr parse_from_string(std::string const& str); +}; diff --git a/tp-json/Node.cpp b/tp-json/Node.cpp new file mode 100644 index 00000000..031d7b24 --- /dev/null +++ b/tp-json/Node.cpp @@ -0,0 +1,6 @@ +#include "Node.hpp" + +std::ostream& operator<<(std::ostream& o, const Node& node) +{ + return o << node.print(); +} \ No newline at end of file diff --git a/tp-json/Node.hpp b/tp-json/Node.hpp new file mode 100644 index 00000000..3e321b6b --- /dev/null +++ b/tp-json/Node.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include "NodeKind.hpp" + +#include +#include +#include +#include +#include + +class NullNode; +class BooleanLeaf; +class NumberLeaf; +class StringLeaf; +class ArrayNode; +class ObjectNode; + +class Node; + +using Node_ptr = std::unique_ptr; + +class Node +{ +private: + const NodeKind _kind; + +protected: + Node(NodeKind kind) + : _kind(kind) + {} + +public: + virtual ~Node() = default; + + NodeKind kind() const { return _kind; } + virtual bool is_of_kind(NodeKind kind) const { return _kind == kind; } + + virtual std::string print() const = 0; + + virtual bool operator==(const Node&) const = 0; + inline bool operator!=(const Node& other) const { return !(*this == other); } + + virtual BooleanLeaf* as_BooleanLeaf() { return nullptr; } + virtual NumberLeaf* as_NumberLeaf() { return nullptr; } + virtual StringLeaf* as_StringLeaf() { return nullptr; } + virtual ArrayNode* as_ArrayNode() { return nullptr; } + virtual ObjectNode* as_ObjectNode() { return nullptr; } + + virtual const BooleanLeaf* as_BooleanLeaf() const { return nullptr; } + virtual const NumberLeaf* as_NumberLeaf() const { return nullptr; } + virtual const StringLeaf* as_StringLeaf() const { return nullptr; } + virtual const ArrayNode* as_ArrayNode() const { return nullptr; } + virtual const ObjectNode* as_ObjectNode() const { return nullptr; } + + virtual size_t height() const { return 0u; } + virtual size_t node_count() const { return 1u; } + + virtual Node_ptr deep_copy() const = 0; + + virtual void dot(std::ostream& o) const + { + o << dot_id() << " [label=\"" << dot_label() << "\"];" << std::endl; + } + + inline std::string dot_id() const + { + const void* address = static_cast(this); + std::stringstream ss; + ss << "n"; + ss << address; + return ss.str(); + } + + virtual std::string dot_label() const = 0; + + virtual size_t child_count() const { return 0; } + + virtual Node* at(std::string const&) { return nullptr; } + virtual const Node* at(std::string const&) const { return nullptr; } + virtual const Node* at(size_t) const { return nullptr; } + virtual Node* at(size_t) { return nullptr; } +}; +std::ostream& operator<<(std::ostream& o, const Node& node); diff --git a/tp-json/NodeKind.hpp b/tp-json/NodeKind.hpp new file mode 100644 index 00000000..b436f326 --- /dev/null +++ b/tp-json/NodeKind.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +enum class NodeKind +{ + BOOLEAN, + NUMBER, + STRING, + ARRAY, + OBJECT +}; + +inline std::ostream& operator<<(std::ostream& o, NodeKind kind) +{ + switch (kind) + { + case NodeKind::BOOLEAN: + return o << "BOOLEAN"; + case NodeKind::NUMBER: + return o << "NUMBER"; + case NodeKind::STRING: + return o << "STRING"; + case NodeKind::OBJECT: + return o << "OBJECT"; + case NodeKind::ARRAY: + return o << "ARRAY"; + } + // Never happens + return o; +} diff --git a/tp-json/NumberLeaf.cpp b/tp-json/NumberLeaf.cpp new file mode 100644 index 00000000..a848804c --- /dev/null +++ b/tp-json/NumberLeaf.cpp @@ -0,0 +1,10 @@ +#include "NumberLeaf.hpp" + +bool NumberLeaf::operator==(const Node& other) const +{ + if (!(other.is_of_kind(kind()))) + { + return false; + } + return (_data == other.as_NumberLeaf()->_data); +} \ No newline at end of file diff --git a/tp-json/NumberLeaf.hpp b/tp-json/NumberLeaf.hpp new file mode 100644 index 00000000..1511541c --- /dev/null +++ b/tp-json/NumberLeaf.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "Node.hpp" + +class NumberLeaf : public Node +{ +private: + int _data; + +public: + inline const int& data() const { return _data; } + + std::string print() const override { return std::to_string(_data); } + + NumberLeaf(int data) + : Node { NodeKind::NUMBER } + , _data { data } + {} + + static std::unique_ptr make_ptr(int data) { return std::make_unique(data); } + + bool operator==(const Node& other) const override; + + NumberLeaf* as_NumberLeaf() override { return this; } + NumberLeaf const* as_NumberLeaf() const override { return this; } + + Node_ptr deep_copy() const override { return make_ptr(data()); } + + std::string dot_label() const override { return std::to_string(_data); } +}; diff --git a/tp-json/ObjectNode.cpp b/tp-json/ObjectNode.cpp new file mode 100644 index 00000000..1af8b27c --- /dev/null +++ b/tp-json/ObjectNode.cpp @@ -0,0 +1 @@ +#include "ObjectNode.hpp" \ No newline at end of file diff --git a/tp-json/ObjectNode.hpp b/tp-json/ObjectNode.hpp new file mode 100644 index 00000000..f94ae241 --- /dev/null +++ b/tp-json/ObjectNode.hpp @@ -0,0 +1,139 @@ +#pragma once + +#include "Node.hpp" + +#include +#include +#include + +class ObjectNode : public Node +{ +private: + std::map _data; + +public: + const std::map& data() const { return _data; } + + ObjectNode() + : Node(NodeKind::OBJECT) + {} + + ObjectNode(std::map data) + : Node(NodeKind::OBJECT) + , _data(std::move(data)) + {} + + static std::unique_ptr make_ptr(std::map data = {}) + { + return std::make_unique(std::move(data)); + } + + void add(std::string key, Node_ptr value) { _data.emplace(std::move(key), std::move(value)); } + + size_t child_count() const override { return _data.size(); } + + std::string print() const override + { + std::string result = "{"; + bool first = true; + for (auto const& el : _data) + { + if (first) + first = false; + else + result += ","; + result += '"' + el.first + "\":"; + result += el.second->print(); + } + result += '}'; + return result; + } + + ObjectNode* as_ObjectNode() override { return this; } + const ObjectNode* as_ObjectNode() const override { return this; } + + inline bool operator==(const Node& other) const override + { + if (!(other.is_of_kind(kind()))) + { + std::cerr << kind() << "!=" << other.kind() << std::endl; + return false; + } + ObjectNode const* other_s = other.as_ObjectNode(); + size_t size = child_count(); + if (other_s->child_count() != size) + { + std::cerr << size << " != " << other_s->child_count() << std::endl; + return false; + } + for (auto& pair : other_s->_data) + { + auto it = _data.find(pair.first); + if (it == _data.end()) + { + std::cerr << pair.first << std::endl; + return false; + } + if (*(it->second) != *(pair.second)) + { + std::cerr << pair.first << std::endl; + std::cerr << *pair.second << std::endl; + std::cerr << *it->second << std::endl; + return false; + } + } + return true; + } + + size_t height() const override + { + return std::accumulate(_data.begin(), _data.end(), 0, + [](size_t i, auto const& pair) + { return std::max(i, 1u + pair.second->height()); }); + } + + virtual size_t node_count() const override + { + return std::accumulate(_data.begin(), _data.end(), 1, + [](size_t i, auto const& pair) { return i + pair.second->node_count(); }); + } + + Node* at(std::string const& key) override + { + if (has_child(key)) + return _data.at(key).get(); + else + return nullptr; + } + const Node* at(std::string const& key) const override + { + if (has_child(key)) + return _data.at(key).get(); + else + return nullptr; + } + + Node_ptr deep_copy() const override + { + auto result = make_ptr(); + for (auto const& [key, child] : this->data()) + result->add(key, child->deep_copy()); + return result; + } + + std::string dot_label() const override { return "{...}"; } + + virtual void dot(std::ostream& o) const + { + o << "subgraph cluster_" << dot_id() << " {style=invis;" << std::endl; + Node::dot(o); + for (auto& [k, v] : _data) + { + v->dot(o); + o << dot_id() << " -> " << v->dot_id() << " [label= \"" << k << "\"];" << std::endl; + } + o << "}" << std::endl; + } + + bool has_child(const std::string& key) const { return _data.count(key); } +}; diff --git a/tp-json/StringLeaf.cpp b/tp-json/StringLeaf.cpp new file mode 100644 index 00000000..f9d28654 --- /dev/null +++ b/tp-json/StringLeaf.cpp @@ -0,0 +1 @@ +#include "StringLeaf.hpp" \ No newline at end of file diff --git a/tp-json/StringLeaf.hpp b/tp-json/StringLeaf.hpp new file mode 100644 index 00000000..30156457 --- /dev/null +++ b/tp-json/StringLeaf.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "Node.hpp" + +class StringLeaf : public Node +{ +private: + std::string _data; + +public: + std::string print() const override + { + std::string result; + result.reserve(_data.size() + 2); + result += '"'; + for (char c : _data) + { + if (c == '\\' || c == '"') + result += '\\'; + result += c; + } + result += '"'; + return result; + } + + StringLeaf(std::string data) + : Node { NodeKind::STRING } + , _data { data } + {} + + static inline std::unique_ptr make_ptr(std::string s) + { + return std::make_unique(std::move(s)); + } + + StringLeaf* as_StringLeaf() override { return this; } + const StringLeaf* as_StringLeaf() const override { return this; } + + inline bool operator==(const Node& other) const override + { + if (!(other.is_of_kind(kind()))) + return false; + return (_data == other.as_StringLeaf()->_data); + } + + inline const std::string& data() const { return _data; } + + Node_ptr deep_copy() const override { return make_ptr(data()); } + + std::string dot_label() const override { return "\\\"" + _data + "\\\""; } +}; diff --git a/tp-json/code_fourni_aux_etudiants/ArrayNode.cpp b/tp-json/code_fourni_aux_etudiants/ArrayNode.cpp new file mode 100644 index 00000000..3232bf18 --- /dev/null +++ b/tp-json/code_fourni_aux_etudiants/ArrayNode.cpp @@ -0,0 +1 @@ +#include "ArrayNode.hpp" \ No newline at end of file diff --git a/tp-json/code_fourni_aux_etudiants/ArrayNode.hpp b/tp-json/code_fourni_aux_etudiants/ArrayNode.hpp new file mode 100644 index 00000000..6f70f09b --- /dev/null +++ b/tp-json/code_fourni_aux_etudiants/ArrayNode.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/tp-json/code_fourni_aux_etudiants/BooleanLeaf.cpp b/tp-json/code_fourni_aux_etudiants/BooleanLeaf.cpp new file mode 100644 index 00000000..530e217c --- /dev/null +++ b/tp-json/code_fourni_aux_etudiants/BooleanLeaf.cpp @@ -0,0 +1 @@ +#include "BooleanLeaf.hpp" \ No newline at end of file diff --git a/tp-json/code_fourni_aux_etudiants/BooleanLeaf.hpp b/tp-json/code_fourni_aux_etudiants/BooleanLeaf.hpp new file mode 100644 index 00000000..6f70f09b --- /dev/null +++ b/tp-json/code_fourni_aux_etudiants/BooleanLeaf.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/tp-json/code_fourni_aux_etudiants/JsonParser.cpp b/tp-json/code_fourni_aux_etudiants/JsonParser.cpp new file mode 100644 index 00000000..3a15dde4 --- /dev/null +++ b/tp-json/code_fourni_aux_etudiants/JsonParser.cpp @@ -0,0 +1,247 @@ +#include "JsonParser.hpp" + +#include +#include +#include +#include + +void JsonParser::extract_spaces() +{ + while (!_in.eof() && std::isspace(_in.peek())) + _in.get(); +} + +bool JsonParser::check_next_char_equals(int c, std::string_view v) +{ + int c2 = _in.peek(); + if (c == c2) + { + _in.get(); + return true; + } + std::cerr << "Unexpected character ("; + if (c2 >= 20 && c2 < 127) { + std::cerr << (char)c2; + } + else if (c2 == -1) { + std::cerr << "EOF"; + } + else { + std::cerr << '\\' << c2; + } + + + if (v != ""){ + std::cerr << "). Expecting a char in \"" << v << "\"." << std::endl; + } + else{ + std::cerr << "). Expecting '" << (char)c << "'." << std::endl; + } + return false; +} + +Node_ptr JsonParser::parse_Node() +{ + extract_spaces(); + int c = _in.peek() + switch (c) + { + case '{': + return parse_ObjectNode(); + case '[': + return parse_ArrayNode(); + case '"': + return parse_StringLeaf(); + // case 'n': + // return parse_constant("null"); + case 'f': + return parse_constant("false"); + case 't': + return parse_constant("true"); + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'e': + case 'E': + case '.': + case '-': + case '+': + return parse_NumberLeaf(); + default: + check_next_char_equals(-123456789, "{[\"nft0123456789eE.-+"); + return nullptr; + } +} + +Node_ptr JsonParser::parse_constant(std::string_view target) +{ + char c[6]; + for (size_t x = 0; x < target.size(); x++) + { + _in >> c[x]; + if (c[x] != target[x]) + { + c[x + 1] = '\0'; + std::cerr << "Expecting JSON value (probably constant " << target << ") at position " + << (_in.tellg() - (long)x) << ". Got string starting with " << c << std::endl; + return nullptr; + } + } + // if (target == "null") + // return NullNode::make_ptr(); + if (target == "true") + return BooleanLeaf::make_ptr(true); + if (target == "false") + return BooleanLeaf::make_ptr(false); + return nullptr; +} + +std::optional JsonParser::extract_string() +{ + extract_spaces(); + check_next_char_equals('"'); + std::string s = ""; + char c = '\0'; + while (((c = _in.get()) != '"') && !_in.eof()) + { + s += c; + if (c == '\\') /* If c is a '\' then the next char is escaped. */ + { + c = _in.get(); + if (_in.eof()) + break; + s += _in.get(); + } + } + + if (_in.eof()) + { + std::cerr << "Found EOF while looking for closing \"." << std::endl; + return std::optional(); + } + return s; +} + +Node_ptr JsonParser::parse_StringLeaf() +{ + auto str = extract_string(); + if (str) + return StringLeaf::make_ptr(std::move(str.value())); + else + return nullptr; +} + +Node_ptr JsonParser::parse_NumberLeaf() +{ + // unsigned starting_pos = _in.tellg(); + + double d; + _in >> d; + // size_t end_pos_double = _in.tellg(); + + return NumberLeaf::make_ptr(d); +} + +Node_ptr JsonParser::parse_ArrayNode() +{ + if (!check_next_char_equals('[')) + return nullptr; + auto arrayNode = ArrayNode::make_ptr(); + extract_spaces(); + if (_in.peek() == ']') + { + _in.get(); + return arrayNode; + } + + do + { + auto child = parse_Node(); + if (child == nullptr) + return nullptr; + else + arrayNode->add(std::move(child)); + extract_spaces(); + } + while (_in.get() == ','); + + _in.unget(); + if (check_next_char_equals(']', ",]")) + return arrayNode; + else + return nullptr; +} + +Node_ptr JsonParser::parse_ObjectNode() +{ + if (!check_next_char_equals('{')) + return nullptr; + auto objectNode = ObjectNode::make_ptr(); + extract_spaces(); + if (_in.peek() == '}') + { + _in.get(); + return objectNode; + } + + do + { + std::optional opt_key = extract_string(); + if (!opt_key) + return nullptr; + extract_spaces(); + if (!check_next_char_equals(':')) + return nullptr; + auto child = parse_Node(); + if (child == nullptr) + return nullptr; + else + objectNode->add(std::move(opt_key.value()), std::move(child)); + extract_spaces(); + } + while (_in.get() == ','); + _in.unget(); + if (check_next_char_equals('}', ",}")) + return objectNode; + else + return nullptr; +} + +Node_ptr JsonParser::run() +{ + return parse_Node(); +} + +Node_ptr JsonParser::parse_from_istream(std::istream& in) +{ + JsonParser parser(in); + Node_ptr parsed_tree = parser.run(); + if (parsed_tree) + return parsed_tree; + else + exit(EXIT_FAILURE); +} + +Node_ptr JsonParser::parse_from_file(std::string const& path) +{ + std::ifstream in(path.c_str(), std::ifstream::in); + if (!in.is_open()) + { + std::cerr << "Could not open file: " << path << std::endl; + exit(EXIT_FAILURE); + } + return parse_from_istream(in); +} + +Node_ptr JsonParser::parse_from_string(std::string const& str) +{ + std::stringstream ss { str }; + return parse_from_istream(ss); +} diff --git a/tp-json/code_fourni_aux_etudiants/JsonParser.hpp b/tp-json/code_fourni_aux_etudiants/JsonParser.hpp new file mode 100644 index 00000000..5f0bb89a --- /dev/null +++ b/tp-json/code_fourni_aux_etudiants/JsonParser.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "ArrayNode.hpp" +#include "BooleanLeaf.hpp" +#include "Node.hpp" +#include "NodeKind.hpp" +#include "NumberLeaf.hpp" +#include "ObjectNode.hpp" +#include "StringLeaf.hpp" + +#include +#include + +class JsonParser +{ +private: + std::istream& _in; + + void extract_spaces(); + bool check_next_char_equals(int c, std::string_view other_possibilities = ""); + + std::optional extract_string(); + + Node_ptr parse_Node(); + Node_ptr parse_constant(std::string_view target); + Node_ptr parse_StringLeaf(); + Node_ptr parse_NumberLeaf(); + Node_ptr parse_ArrayNode(); + Node_ptr parse_ObjectNode(); + +public: + JsonParser(std::istream& in) + : _in(in) + {} + + Node_ptr run(); + + static Node_ptr parse_from_istream(std::istream& in); + static Node_ptr parse_from_file(std::string const& path); + static Node_ptr parse_from_string(std::string const& str); +}; diff --git a/tp-json/code_fourni_aux_etudiants/Node.cpp b/tp-json/code_fourni_aux_etudiants/Node.cpp new file mode 100644 index 00000000..d4c476f4 --- /dev/null +++ b/tp-json/code_fourni_aux_etudiants/Node.cpp @@ -0,0 +1 @@ +#include "Node.hpp" diff --git a/tp-json/code_fourni_aux_etudiants/Node.hpp b/tp-json/code_fourni_aux_etudiants/Node.hpp new file mode 100644 index 00000000..6f70f09b --- /dev/null +++ b/tp-json/code_fourni_aux_etudiants/Node.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/tp-json/code_fourni_aux_etudiants/NodeKind.hpp b/tp-json/code_fourni_aux_etudiants/NodeKind.hpp new file mode 100644 index 00000000..b436f326 --- /dev/null +++ b/tp-json/code_fourni_aux_etudiants/NodeKind.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +enum class NodeKind +{ + BOOLEAN, + NUMBER, + STRING, + ARRAY, + OBJECT +}; + +inline std::ostream& operator<<(std::ostream& o, NodeKind kind) +{ + switch (kind) + { + case NodeKind::BOOLEAN: + return o << "BOOLEAN"; + case NodeKind::NUMBER: + return o << "NUMBER"; + case NodeKind::STRING: + return o << "STRING"; + case NodeKind::OBJECT: + return o << "OBJECT"; + case NodeKind::ARRAY: + return o << "ARRAY"; + } + // Never happens + return o; +} diff --git a/tp-json/code_fourni_aux_etudiants/NumberLeaf.cpp b/tp-json/code_fourni_aux_etudiants/NumberLeaf.cpp new file mode 100644 index 00000000..ccc70860 --- /dev/null +++ b/tp-json/code_fourni_aux_etudiants/NumberLeaf.cpp @@ -0,0 +1 @@ +#include "NumberLeaf.hpp" diff --git a/tp-json/code_fourni_aux_etudiants/NumberLeaf.hpp b/tp-json/code_fourni_aux_etudiants/NumberLeaf.hpp new file mode 100644 index 00000000..6f70f09b --- /dev/null +++ b/tp-json/code_fourni_aux_etudiants/NumberLeaf.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/tp-json/code_fourni_aux_etudiants/ObjectNode.cpp b/tp-json/code_fourni_aux_etudiants/ObjectNode.cpp new file mode 100644 index 00000000..1af8b27c --- /dev/null +++ b/tp-json/code_fourni_aux_etudiants/ObjectNode.cpp @@ -0,0 +1 @@ +#include "ObjectNode.hpp" \ No newline at end of file diff --git a/tp-json/code_fourni_aux_etudiants/ObjectNode.hpp b/tp-json/code_fourni_aux_etudiants/ObjectNode.hpp new file mode 100644 index 00000000..6f70f09b --- /dev/null +++ b/tp-json/code_fourni_aux_etudiants/ObjectNode.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/tp-json/code_fourni_aux_etudiants/StringLeaf.cpp b/tp-json/code_fourni_aux_etudiants/StringLeaf.cpp new file mode 100644 index 00000000..f9d28654 --- /dev/null +++ b/tp-json/code_fourni_aux_etudiants/StringLeaf.cpp @@ -0,0 +1 @@ +#include "StringLeaf.hpp" \ No newline at end of file diff --git a/tp-json/code_fourni_aux_etudiants/StringLeaf.hpp b/tp-json/code_fourni_aux_etudiants/StringLeaf.hpp new file mode 100644 index 00000000..6f70f09b --- /dev/null +++ b/tp-json/code_fourni_aux_etudiants/StringLeaf.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/tp-json/enonce.md b/tp-json/enonce.md new file mode 100644 index 00000000..8c4d1b66 --- /dev/null +++ b/tp-json/enonce.md @@ -0,0 +1,81 @@ +# TP4 - Héritage + +Le but de ce TP est d'implémenter les classes permettant de représenter en mémoire un document JSON (JavaScript Object Notation). + + +## Test Driven Development + +Ce TP suit à nouveau en TDD, mais cette fois on utilisera des `ctest`. Chaque test est un programme indépendant, et il est réussi s'il s'éxecute sans erreur. Les programmes se trouvent dans le répertoire `tests`, et doivent être fait par ordre croissant. + +Pour activer les tests, décommenter la ligne suivante du `CMakeLists.txt` à la racine du répertoire git. +``` +enable_testing() +``` + +Après reconfiguration, une nouvelle ligne apparait dans la barre en bas de vscode. Vous pouvez lancer la suite de tests en cliquant dessus. Vous pouvez aussi utiliser les boutons usuels (*Build*, *Run* et *Debug*) pour compiler ou éxécuter un test en particulier. + + +Un `ctest` est un fichier qui contient un petit programme indépendant (par exemple `tests/01_compile.cpp`), qui correspond à deux tests: +- Un test de compilation (par exemple `build:01_compile.cpp`): il est réussi si le compilateur arrive a compiler le fichier. +- Un text d'éxecution (par exemple `run:01_compile.cpp`): le test est réussi si le programme s'éxecute sans erreur. + + +## Documents JSON + + +Un document JSON ressemble typiquement à (fichier `tests/json/cpp2022.json`): +```json +{ + "name":"C++", + "level":"M1", + "mandatory":true, + "teachers":["Céline","Matthias","Victor"], + "year": 2022, + "sessions":[ + {"kind":"TP", "number":1, "week":1}, + {"kind":"TP", "number":2, "week":2}, + {"kind":"TP", "number":3, "week":3}, + {"kind":"TP", "number":4, "week":4}, + {"kind":"Project", "weeks":[5,6,7,8,9,10,11,12]} + ] +} +``` + +Une valeur JSON est soit: +1. un *booléen*, par exemple `true`; +2. un *nombre*, par exemple `2022`; +3. une *chaîne de caractère*, par exemple `"C++"`; +4. une *liste* de valeurs JSON entre `[`...`]` et séparées par des virgules, par exemple `["Céline","Matthias","Victor"]`; +5. un *dictionnaire* entre `{`...`}` qui associe des clefs (chaîne de caractère avant le `:`) à des valeurs JSON (après le `:`), par exemple `{"kind":"Project", "weeks":[5,6,7,8,9,10,11,12]}` associe la clef `"kind"` à la valeur `"Project"`, la clef `"weeks"` à la valeur `[5,6,7,8,9,10,11,12]` + +Notez qu'on ne suppose aucune cohérence particulière de typage. Par exemple les valeurs JSON dans une liste peuvent être de types différents, par exemple `[1, "2i",{"imag":3, "real":4}]` pourrait représenter la liste des trois nombres complexes $1$, $2i$ et $3i+4$. + +Un document JSON est simplement un fichier qui contient une valeur JSON, usuellement un dictionnaire. D'autres documents json se trouvent dans le dossier `json`. La plupart sont des petits exemples à des fins de tests (**ne les modifiez pas!**). +Au contraire, le document `json/pokedex.json` est représentatif de ce à quoi ressemble un document JSON réel, et sera utilisé dans les tests plus avancés. + + +## Arbres + +Un document peut se voir comme un arbre: +- Les booléens, les entiers et chaînes de caractères sont des feuilles de l'arbre. +- les listes et les dictionnaires sont des noeuds internes et ont pour fils chacune des valeurs à l'intérieur. + +Par exemple, le document donné en début de TP se représente: +![Représentation du document json cpp2022.json](example_cpp2022_as_image.svg) + + +## Code à produire + +Un document JSON sera représenté en mémoire comme un arbre dont les noeuds sont polymorphes: +- La classe `Node` sera la classe principale pour représenter un noeud dont on ne connaît pas le type exact. +- Les classes `BooleanLeaf`, `NumberLeaf`, `StringLeaf`, `ArrayNode`, `ObjectNode` représenterons les différents types de noeuds. +- Le type `Node_ptr` sera utilisée pour faire référence/pointer vers les enfants d'un noeud. Vous devrez choisir le type approprié. +- Le type `NodeKind` est fourni, c'est une `enum` listant les différents types de noeuds. A l'éxecution, il sera utilisé par les `Node`'s pour indiquer leur type réel. + +Quand c'est pertinent, on factorisera le code en utilisant l'héritage. +Par exemple, si on remarque que plusieurs classes partagent des fonctionnalités, il faudra les centraliser dans une classe intermédiaire (entre `Node` et les sous-classes concrètes). + +## Parseur + +Un parseur de JSON est fourni (classe `JsonParser` dans le fichier `JsonParser.cpp`), et normalement vous n'aurez pas besoin de le modifier. Il pourra éventuellement être utile de regarder ce fichier à partir des tests qui l'utilisent. + diff --git a/tp-json/example_cpp2022_as_image.svg b/tp-json/example_cpp2022_as_image.svg new file mode 100644 index 00000000..2d260509 --- /dev/null +++ b/tp-json/example_cpp2022_as_image.svg @@ -0,0 +1,498 @@ + + + + + + +G + + +cluster_n0x5605b4a52f00 + + +cluster_n0x5605b4a55570 + + +cluster_n0x5605b4a55cb0 + + +cluster_n0x5605b4a55d90 + + +cluster_n0x5605b4a55b10 + + +cluster_n0x5605b4a55940 + + +cluster_n0x5605b4a55760 + + +cluster_n0x5605b4a555a0 + + +cluster_n0x5605b4a55370 + + + +n0x5605b4a52f00 + +{...} + + + +n0x5605b4a55270 + +"M1" + + + +n0x5605b4a52f00->n0x5605b4a55270 + + +level + + + +n0x5605b4a55300 + +true + + + +n0x5605b4a52f00->n0x5605b4a55300 + + +mandatory + + + +n0x5605b4a551e0 + +"C++" + + + +n0x5605b4a52f00->n0x5605b4a551e0 + + +name + + + +n0x5605b4a55570 + +[...] + + + +n0x5605b4a52f00->n0x5605b4a55570 + + +sessions + + + +n0x5605b4a55370 + +[...] + + + +n0x5605b4a52f00->n0x5605b4a55370 + + +teachers + + + +n0x5605b4a55440 + +2022 + + + +n0x5605b4a52f00->n0x5605b4a55440 + + +year + + + +n0x5605b4a55cb0 + +{...} + + + +n0x5605b4a55570->n0x5605b4a55cb0 + + + + + +n0x5605b4a55b10 + +{...} + + + +n0x5605b4a55570->n0x5605b4a55b10 + + + + + +n0x5605b4a55940 + +{...} + + + +n0x5605b4a55570->n0x5605b4a55940 + + + + + +n0x5605b4a55760 + +{...} + + + +n0x5605b4a55570->n0x5605b4a55760 + + + + + +n0x5605b4a555a0 + +{...} + + + +n0x5605b4a55570->n0x5605b4a555a0 + + + + + +n0x5605b4a55d00 + +"Project" + + + +n0x5605b4a55cb0->n0x5605b4a55d00 + + +kind + + + +n0x5605b4a55d90 + +[...] + + + +n0x5605b4a55cb0->n0x5605b4a55d90 + + +weeks + + + +n0x5605b4a55f70 + +12 + + + +n0x5605b4a55d90->n0x5605b4a55f70 + + + + + +n0x5605b4a55f50 + +11 + + + +n0x5605b4a55d90->n0x5605b4a55f50 + + + + + +n0x5605b4a55f30 + +10 + + + +n0x5605b4a55d90->n0x5605b4a55f30 + + + + + +n0x5605b4a55ec0 + +9 + + + +n0x5605b4a55d90->n0x5605b4a55ec0 + + + + + +n0x5605b4a55e70 + +8 + + + +n0x5605b4a55d90->n0x5605b4a55e70 + + + + + +n0x5605b4a55e30 + +7 + + + +n0x5605b4a55d90->n0x5605b4a55e30 + + + + + +n0x5605b4a55e50 + +6 + + + +n0x5605b4a55d90->n0x5605b4a55e50 + + + + + +n0x5605b4a55e10 + +5 + + + +n0x5605b4a55d90->n0x5605b4a55e10 + + + + + +n0x5605b4a55b60 + +"TP" + + + +n0x5605b4a55b10->n0x5605b4a55b60 + + +kind + + + +n0x5605b4a55920 + +4 + + + +n0x5605b4a55b10->n0x5605b4a55920 + + +number + + + +n0x5605b4a55c90 + +4 + + + +n0x5605b4a55b10->n0x5605b4a55c90 + + +week + + + +n0x5605b4a55990 + +"TP" + + + +n0x5605b4a55940->n0x5605b4a55990 + + +kind + + + +n0x5605b4a55740 + +3 + + + +n0x5605b4a55940->n0x5605b4a55740 + + +number + + + +n0x5605b4a55ac0 + +3 + + + +n0x5605b4a55940->n0x5605b4a55ac0 + + +week + + + +n0x5605b4a557b0 + +"TP" + + + +n0x5605b4a55760->n0x5605b4a557b0 + + +kind + + + +n0x5605b4a55890 + +2 + + + +n0x5605b4a55760->n0x5605b4a55890 + + +number + + + +n0x5605b4a55900 + +2 + + + +n0x5605b4a55760->n0x5605b4a55900 + + +week + + + +n0x5605b4a555f0 + +"TP" + + + +n0x5605b4a555a0->n0x5605b4a555f0 + + +kind + + + +n0x5605b4a553e0 + +1 + + + +n0x5605b4a555a0->n0x5605b4a553e0 + + +number + + + +n0x5605b4a55720 + +1 + + + +n0x5605b4a555a0->n0x5605b4a55720 + + +week + + + +n0x5605b4a55460 + +"Victor" + + + +n0x5605b4a55370->n0x5605b4a55460 + + + + + +n0x5605b4a55400 + +"Matthias" + + + +n0x5605b4a55370->n0x5605b4a55400 + + + + + +n0x5605b4a553a0 + +"Céline" + + + +n0x5605b4a55370->n0x5605b4a553a0 + + + + + diff --git a/tp-json/tests/01_compile.cpp b/tp-json/tests/01_compile.cpp new file mode 100644 index 00000000..c3e824ba --- /dev/null +++ b/tp-json/tests/01_compile.cpp @@ -0,0 +1,10 @@ +/* This files exists to check that the files compile. */ +#include "../Node.hpp" +#include "custom_assert.hpp" + +int main() +{ + Node* node1 = nullptr; + Node* node2 = nullptr; + ASSERT_EQUAL(node1, node2); +} diff --git a/tp-json/tests/02_basic_boolean.cpp b/tp-json/tests/02_basic_boolean.cpp new file mode 100644 index 00000000..8a7fe5b9 --- /dev/null +++ b/tp-json/tests/02_basic_boolean.cpp @@ -0,0 +1,16 @@ +#include "../BooleanLeaf.hpp" +#include "custom_assert.hpp" + +int main() +{ + BooleanLeaf p { true }; + ASSERT_EQUAL(p.kind(), NodeKind::BOOLEAN); + ASSERT_EQUAL(p.print(), "true"); + + Node& r = p; + ASSERT_EQUAL(r.kind(), NodeKind::BOOLEAN); + ASSERT_EQUAL(r.print(), "true"); + + BooleanLeaf p2 { false }; + ASSERT_EQUAL(p2.print(), "false"); +} diff --git a/tp-json/tests/03_basic_number.cpp b/tp-json/tests/03_basic_number.cpp new file mode 100644 index 00000000..8162cc37 --- /dev/null +++ b/tp-json/tests/03_basic_number.cpp @@ -0,0 +1,13 @@ +#include "../NumberLeaf.hpp" +#include "custom_assert.hpp" + +int main() +{ + NumberLeaf p { 1 }; + ASSERT_EQUAL(p.kind(), NodeKind::NUMBER); + ASSERT_EQUAL(p.print(), "1"); + + Node& r = p; + ASSERT_EQUAL(r.kind(), NodeKind::NUMBER); + ASSERT_EQUAL(r.print(), "1"); +} diff --git a/tp-json/tests/04_basic_string.cpp b/tp-json/tests/04_basic_string.cpp new file mode 100644 index 00000000..f3bf7000 --- /dev/null +++ b/tp-json/tests/04_basic_string.cpp @@ -0,0 +1,13 @@ +#include "../StringLeaf.hpp" +#include "custom_assert.hpp" + +int main() +{ + StringLeaf p { "Hello world!" }; + ASSERT_EQUAL(p.kind(), NodeKind::STRING); + ASSERT_EQUAL(p.print(), "\"Hello world!\""); + + Node& r = p; + ASSERT_EQUAL(r.kind(), NodeKind::STRING); + ASSERT_EQUAL(r.print(), "\"Hello world!\""); +} diff --git a/tp-json/tests/05_basic_array.cpp b/tp-json/tests/05_basic_array.cpp new file mode 100644 index 00000000..1b18db71 --- /dev/null +++ b/tp-json/tests/05_basic_array.cpp @@ -0,0 +1,13 @@ +#include "../ArrayNode.hpp" +#include "custom_assert.hpp" + +int main() +{ + ArrayNode p {}; + ASSERT_EQUAL(p.kind(), NodeKind::ARRAY); + ASSERT_EQUAL(p.print(), "[]"); + + Node& r = p; + ASSERT_EQUAL(r.kind(), NodeKind::ARRAY); + ASSERT_EQUAL(r.print(), "[]"); +} diff --git a/tp-json/tests/06_basic_object.cpp b/tp-json/tests/06_basic_object.cpp new file mode 100644 index 00000000..0c36e351 --- /dev/null +++ b/tp-json/tests/06_basic_object.cpp @@ -0,0 +1,13 @@ +#include "../ObjectNode.hpp" +#include "custom_assert.hpp" + +int main() +{ + ObjectNode p {}; + ASSERT_EQUAL(p.kind(), NodeKind::OBJECT); + ASSERT_EQUAL(p.print(), "{}"); + + Node& r = p; + ASSERT_EQUAL(r.kind(), NodeKind::OBJECT); + ASSERT_EQUAL(r.print(), "{}"); +} diff --git a/tp-json/tests/10_make_ptr.cpp b/tp-json/tests/10_make_ptr.cpp new file mode 100644 index 00000000..7ffd684f --- /dev/null +++ b/tp-json/tests/10_make_ptr.cpp @@ -0,0 +1,20 @@ +#include "../ArrayNode.hpp" +#include "../BooleanLeaf.hpp" +#include "../Node.hpp" +#include "../NodeKind.hpp" +#include "../NumberLeaf.hpp" +#include "../ObjectNode.hpp" +#include "../StringLeaf.hpp" +#include "custom_assert.hpp" + +#include + +int main() +{ + Node_ptr null_node_ptr = nullptr; + Node_ptr bool_node_ptr = BooleanLeaf::make_ptr(true); + Node_ptr int_node_ptr = NumberLeaf::make_ptr(1); + Node_ptr str_node_ptr = StringLeaf::make_ptr("Hello world"); + Node_ptr arr_node_ptr = ArrayNode::make_ptr(); + Node_ptr obj_node_ptr = ObjectNode::make_ptr(); +} diff --git a/tp-json/tests/11_array_add.cpp b/tp-json/tests/11_array_add.cpp new file mode 100644 index 00000000..399c144a --- /dev/null +++ b/tp-json/tests/11_array_add.cpp @@ -0,0 +1,28 @@ +#include "../ArrayNode.hpp" +#include "../BooleanLeaf.hpp" +#include "../Node.hpp" +#include "../NodeKind.hpp" +#include "../NumberLeaf.hpp" +#include "../ObjectNode.hpp" +#include "../StringLeaf.hpp" +#include "custom_assert.hpp" + +int main() +{ + auto array_node_ptr = ArrayNode::make_ptr(); + size_t size = array_node_ptr->child_count(); + ASSERT_EQUAL(size, 0u); + + array_node_ptr->add(StringLeaf::make_ptr("H")); + array_node_ptr->add(NumberLeaf::make_ptr(3110)); + array_node_ptr->add(StringLeaf::make_ptr("!")); + + ASSERT_EQUAL(array_node_ptr->child_count(), 3u); + + // This is a raw literal, go check it out https://en.cppreference.com/w/cpp/language/string_literal */ + std::string target = R"---(["H",3110,"!"])---"; + // ^^^^^^ ^^^^^ + // The parts marked with ^ above are not part of the string; + + ASSERT_EQUAL(array_node_ptr->print(), target); +} diff --git a/tp-json/tests/12_object_add.cpp b/tp-json/tests/12_object_add.cpp new file mode 100644 index 00000000..705914ab --- /dev/null +++ b/tp-json/tests/12_object_add.cpp @@ -0,0 +1,34 @@ +#include "../ArrayNode.hpp" +#include "../BooleanLeaf.hpp" +#include "../Node.hpp" +#include "../NodeKind.hpp" +#include "../NumberLeaf.hpp" +#include "../ObjectNode.hpp" +#include "../StringLeaf.hpp" +#include "custom_assert.hpp" + +int main() +{ + auto object_node_ptr = ObjectNode::make_ptr(); + size_t size = object_node_ptr->child_count(); + ASSERT_EQUAL(size, 0u); + + object_node_ptr->add("H", NumberLeaf::make_ptr(3110)); + object_node_ptr->add(" ", BooleanLeaf::make_ptr(true)); + + auto array_node_ptr = ArrayNode::make_ptr(); + array_node_ptr->add(NumberLeaf::make_ptr(0)); + array_node_ptr->add(StringLeaf::make_ptr("rld!")); + array_node_ptr->add(ArrayNode::make_ptr()); + array_node_ptr->add(ObjectNode::make_ptr()); + object_node_ptr->add("W", std::move(array_node_ptr)); + + ASSERT_EQUAL(object_node_ptr->child_count(), 3u); + + // This is a raw literal, go check it out https://en.cppreference.com/w/cpp/language/string_literal */ + std::string target = R"---({" ":true,"H":3110,"W":[0,"rld!",[],{}]})---"; + // ^^^^^^ ^^^^^ + // The parts marked with ^ above are not part of the string; + + ASSERT_EQUAL(object_node_ptr->print(), target); +} \ No newline at end of file diff --git a/tp-json/tests/13_height_and_nodecount.cpp b/tp-json/tests/13_height_and_nodecount.cpp new file mode 100644 index 00000000..f88104aa --- /dev/null +++ b/tp-json/tests/13_height_and_nodecount.cpp @@ -0,0 +1,30 @@ +#include "../ArrayNode.hpp" +#include "../BooleanLeaf.hpp" +#include "../Node.hpp" +#include "../NodeKind.hpp" +#include "../NumberLeaf.hpp" +#include "../ObjectNode.hpp" +#include "../StringLeaf.hpp" +#include "custom_assert.hpp" + +#include + +int main() +{ + Node_ptr true_node_ptr = BooleanLeaf::make_ptr(true); + Node_ptr false_node_ptr = BooleanLeaf::make_ptr(false); + auto array_node_ptr = ArrayNode::make_ptr(); + array_node_ptr->add(std::move(true_node_ptr)); + array_node_ptr->add(std::move(false_node_ptr)); + ASSERT_EQUAL(array_node_ptr->height(), 1u); + ASSERT_EQUAL(array_node_ptr->node_count(), 3u); + + auto object_node_ptr = ObjectNode::make_ptr(); + Node_ptr string_node_ptr = StringLeaf::make_ptr("Hello world"); + Node_ptr int_node_ptr = NumberLeaf::make_ptr(1); + object_node_ptr->add("array", std::move(array_node_ptr)); + object_node_ptr->add("string", std::move(string_node_ptr)); + object_node_ptr->add("int", std::move(int_node_ptr)); + ASSERT_EQUAL(object_node_ptr->height(), 2u); + ASSERT_EQUAL(object_node_ptr->node_count(), 6u); +} \ No newline at end of file diff --git a/tp-json/tests/14_object_dico.cpp b/tp-json/tests/14_object_dico.cpp new file mode 100644 index 00000000..b6f419de --- /dev/null +++ b/tp-json/tests/14_object_dico.cpp @@ -0,0 +1,24 @@ +#include "../ArrayNode.hpp" +#include "../BooleanLeaf.hpp" +#include "../Node.hpp" +#include "../NodeKind.hpp" +#include "../NumberLeaf.hpp" +#include "../ObjectNode.hpp" +#include "../StringLeaf.hpp" +#include "custom_assert.hpp" + +int main() +{ + auto object_node_ptr = ObjectNode::make_ptr(); + size_t size = object_node_ptr->child_count(); + ASSERT_EQUAL(size, 0u); + + object_node_ptr->add("key1", NumberLeaf::make_ptr(42)); + object_node_ptr->add("key1", BooleanLeaf::make_ptr(true)); + object_node_ptr->add("key2", StringLeaf::make_ptr("Hello World!")); + object_node_ptr->add("key2", ArrayNode::make_ptr()); + object_node_ptr->add("key2", StringLeaf::make_ptr("World, hello!")); + + ASSERT_EQUAL(object_node_ptr->height(), 1u); + ASSERT_EQUAL(object_node_ptr->node_count(), 3u); +} \ No newline at end of file diff --git a/tp-json/tests/21_parser_leaf.cpp b/tp-json/tests/21_parser_leaf.cpp new file mode 100644 index 00000000..933b4f44 --- /dev/null +++ b/tp-json/tests/21_parser_leaf.cpp @@ -0,0 +1,54 @@ +#include "../JsonParser.hpp" +#include "custom_assert.hpp" + +#include + +int main() +{ + std::string json_dir = "json/"; + + { + std::string filename = json_dir + "boolean_true.json"; + std::cerr << "Starting test with: " << filename << std::endl; + Node_ptr node = JsonParser::parse_from_file(filename); + ASSERT_EQUAL(node->height(), 0u); + ASSERT_EQUAL(node->node_count(), 1u); + ASSERT_EQUAL(node->kind(), NodeKind::BOOLEAN); + } + + { + std::string filename = json_dir + "number_42.json"; + std::cerr << "Starting test with: " << filename << std::endl; + Node_ptr node = JsonParser::parse_from_file(filename); + ASSERT_EQUAL(node->height(), 0u); + ASSERT_EQUAL(node->node_count(), 1u); + ASSERT_EQUAL(node->kind(), NodeKind::NUMBER); + } + + { + std::string filename = json_dir + "string_hello.json"; + std::cerr << "Starting test with: " << filename << std::endl; + Node_ptr node = JsonParser::parse_from_file(filename); + ASSERT_EQUAL(node->height(), 0u); + ASSERT_EQUAL(node->node_count(), 1u); + ASSERT_EQUAL(node->kind(), NodeKind::STRING); + } + + { + std::string filename = json_dir + "array_empty.json"; + std::cerr << "Starting test with: " << filename << std::endl; + Node_ptr node = JsonParser::parse_from_file(filename); + ASSERT_EQUAL(node->height(), 0u); + ASSERT_EQUAL(node->node_count(), 1u); + ASSERT_EQUAL(node->kind(), NodeKind::ARRAY); + } + + { + std::string filename = json_dir + "object_empty.json"; + std::cerr << "Starting test with: " << filename << std::endl; + Node_ptr node = JsonParser::parse_from_file(filename); + ASSERT_EQUAL(node->height(), 0u); + ASSERT_EQUAL(node->node_count(), 1u); + ASSERT_EQUAL(node->kind(), NodeKind::OBJECT); + } +} \ No newline at end of file diff --git a/tp-json/tests/22_parser_array.cpp b/tp-json/tests/22_parser_array.cpp new file mode 100644 index 00000000..c57419b9 --- /dev/null +++ b/tp-json/tests/22_parser_array.cpp @@ -0,0 +1,27 @@ +#include "../JsonParser.hpp" +#include "custom_assert.hpp" + +#include + +int main() +{ + std::string json_dir = "json/"; + + { + std::string filename = json_dir + "array_range10.json"; + std::cerr << "Starting test with: " << filename << std::endl; + Node_ptr node = JsonParser::parse_from_file(filename); + ASSERT_EQUAL(node->height(), 1u); + ASSERT_EQUAL(node->node_count(), 11u); + ASSERT_EQUAL(node->kind(), NodeKind::ARRAY); + } + + { + std::string filename = json_dir + "array_hexadecimal.json"; + std::cerr << "Starting test with: " << filename << std::endl; + Node_ptr node = JsonParser::parse_from_file(filename); + ASSERT_EQUAL(node->height(), 4u); + ASSERT_EQUAL(node->node_count(), 31u); + ASSERT_EQUAL(node->kind(), NodeKind::ARRAY); + } +} \ No newline at end of file diff --git a/tp-json/tests/23_parser_object.cpp b/tp-json/tests/23_parser_object.cpp new file mode 100644 index 00000000..b5f807b5 --- /dev/null +++ b/tp-json/tests/23_parser_object.cpp @@ -0,0 +1,27 @@ +#include "../JsonParser.hpp" +#include "custom_assert.hpp" + +#include + +int main() +{ + std::string json_dir = "json/"; + + { + std::string filename = json_dir + "object_empty.json"; + std::cerr << "Starting test with: " << filename << std::endl; + Node_ptr node = JsonParser::parse_from_file(filename); + ASSERT_EQUAL(node->height(), 0u); + ASSERT_EQUAL(node->node_count(), 1u); + ASSERT_EQUAL(node->kind(), NodeKind::OBJECT); + } + + { + std::string filename = json_dir + "object_alphabet.json"; + std::cerr << "Starting test with: " << filename << std::endl; + Node_ptr node = JsonParser::parse_from_file(filename); + ASSERT_EQUAL(node->height(), 1u); + ASSERT_EQUAL(node->node_count(), 27u); + ASSERT_EQUAL(node->kind(), NodeKind::OBJECT); + } +} \ No newline at end of file diff --git a/tp-json/tests/24_parser_pokedex.cpp b/tp-json/tests/24_parser_pokedex.cpp new file mode 100644 index 00000000..66e20503 --- /dev/null +++ b/tp-json/tests/24_parser_pokedex.cpp @@ -0,0 +1,19 @@ +#include "../JsonParser.hpp" +#include "custom_assert.hpp" + +#include + +int main() +{ + std::string json_dir = "json/"; + + { + std::string filename = json_dir + "pokedex.json"; + std::cerr << "Starting test with: " << filename << std::endl; + Node_ptr node = JsonParser::parse_from_file(filename); + ASSERT_EQUAL(node->child_count(), 1u); + ASSERT_EQUAL(node->height(), 5u); + ASSERT_EQUAL(node->node_count(), 3779u); + ASSERT_EQUAL(node->kind(), NodeKind::OBJECT); + } +} \ No newline at end of file diff --git a/tp-json/tests/30_output_operator.cpp b/tp-json/tests/30_output_operator.cpp new file mode 100644 index 00000000..a72f053b --- /dev/null +++ b/tp-json/tests/30_output_operator.cpp @@ -0,0 +1,13 @@ +#include "../ArrayNode.hpp" +#include "custom_assert.hpp" + +#include + +int main() +{ + // This only tests for the existence of the operator<< + // in order to have ASSERT_EQUAL to compile + // We do not impose the format: cf optional questions. + Node_ptr node = ArrayNode::make_ptr(); + std::cout << (*node) << std::endl; +} diff --git a/tp-json/tests/31_equality.cpp b/tp-json/tests/31_equality.cpp new file mode 100644 index 00000000..f3254f93 --- /dev/null +++ b/tp-json/tests/31_equality.cpp @@ -0,0 +1,50 @@ +#include "../JsonParser.hpp" +#include "custom_assert.hpp" + +int main() +{ + std::string json_dir = "json/"; + + { + std::string filename = json_dir + "boolean_true.json"; + std::cerr << "Starting test with: " << filename << std::endl; + Node_ptr node = JsonParser::parse_from_file(filename); + ASSERT_EQUAL(*node, *BooleanLeaf::make_ptr(true)); + } + { + std::string filename = json_dir + "number_42.json"; + std::cerr << "Starting test with: " << filename << std::endl; + Node_ptr node = JsonParser::parse_from_file(filename); + ASSERT_EQUAL(*node, *NumberLeaf::make_ptr(42)); + } + { + std::string filename = json_dir + "string_hello.json"; + std::cerr << "Starting test with: " << filename << std::endl; + Node_ptr node = JsonParser::parse_from_file(filename); + ASSERT_EQUAL(*node, *StringLeaf::make_ptr("Hello")); + } + { + std::string filename = json_dir + "object_alphabet.json"; + std::cerr << "Starting test with: " << filename << std::endl; + Node_ptr node = JsonParser::parse_from_file(filename); + auto target = ObjectNode::make_ptr(); + for (char c = 'a'; c <= 'z'; c++) + target->add(std::string(1, c), NumberLeaf::make_ptr(c)); + ASSERT_EQUAL(*node, *target); + } + { + std::string filename = json_dir + "array_range10.json"; + std::cerr << "Starting test with: " << filename << std::endl; + Node_ptr node = JsonParser::parse_from_file(filename); + auto target = ArrayNode::make_ptr(); + for (unsigned i = 0; i < 10; i++) + target->add(NumberLeaf::make_ptr(i)); + ASSERT_EQUAL(*node, *target); + } + { + std::string filename = json_dir + "pokedex.json"; + std::cerr << "Starting test with: " << filename << std::endl; + Node_ptr node = JsonParser::parse_from_file(filename); + ASSERT_EQUAL(*node, *node); + } +} \ No newline at end of file diff --git a/tp-json/tests/32_printparse_consistency.cpp b/tp-json/tests/32_printparse_consistency.cpp new file mode 100644 index 00000000..b5bfae73 --- /dev/null +++ b/tp-json/tests/32_printparse_consistency.cpp @@ -0,0 +1,19 @@ +#include "../JsonParser.hpp" +#include "custom_assert.hpp" + +int main() +{ + std::string json_dir = "json/"; + + std::vector names = { "cpp2022.json", "pokedex.json", "string_nasty.json" }; + + for (const std::string& name : names) + { + std::string filename = json_dir + name; + std::cerr << "Starting test with: \"" << filename << '"' << std::endl; + Node_ptr node1 = JsonParser::parse_from_file(filename); + std::string node1_as_string = node1->print(); + Node_ptr node2 = JsonParser::parse_from_string(node1_as_string); + ASSERT_EQUAL(*node1, *node2); + } +} \ No newline at end of file diff --git a/tp-json/tests/40_explicit_cast.cpp b/tp-json/tests/40_explicit_cast.cpp new file mode 100644 index 00000000..5ee7f0fb --- /dev/null +++ b/tp-json/tests/40_explicit_cast.cpp @@ -0,0 +1,41 @@ +#include "../ArrayNode.hpp" +#include "../BooleanLeaf.hpp" +#include "../Node.hpp" +#include "../NodeKind.hpp" +#include "../NumberLeaf.hpp" +#include "../ObjectNode.hpp" +#include "../StringLeaf.hpp" +#include "custom_assert.hpp" + +int main() +{ + + { + Node_ptr node = BooleanLeaf::make_ptr(true); + ASSERT_UNEQUAL(node->as_BooleanLeaf(), nullptr); + ASSERT_EQUAL(node->as_NumberLeaf(), nullptr); + ASSERT_EQUAL(node->as_StringLeaf(), nullptr); + ASSERT_EQUAL(node->as_ArrayNode(), nullptr); + ASSERT_EQUAL(node->as_ObjectNode(), nullptr); + } + { + Node_ptr node = NumberLeaf::make_ptr(1); + ASSERT_UNEQUAL(node->as_NumberLeaf(), nullptr); + ASSERT_EQUAL(node->as_BooleanLeaf(), nullptr); + ASSERT_EQUAL(node->as_StringLeaf(), nullptr); + ASSERT_EQUAL(node->as_ArrayNode(), nullptr); + ASSERT_EQUAL(node->as_ObjectNode(), nullptr); + } + { + Node_ptr node = StringLeaf::make_ptr("Hello world"); + ASSERT_UNEQUAL(node->as_StringLeaf(), nullptr); + } + { + Node_ptr node = ArrayNode::make_ptr(); + ASSERT_UNEQUAL(node->as_ArrayNode(), nullptr); + } + { + Node_ptr node = ObjectNode::make_ptr(); + ASSERT_UNEQUAL(node->as_ObjectNode(), nullptr); + } +} \ No newline at end of file diff --git a/tp-json/tests/41_explicit_cast_const.cpp b/tp-json/tests/41_explicit_cast_const.cpp new file mode 100644 index 00000000..84733471 --- /dev/null +++ b/tp-json/tests/41_explicit_cast_const.cpp @@ -0,0 +1,45 @@ +#include "../ArrayNode.hpp" +#include "../BooleanLeaf.hpp" +#include "../Node.hpp" +#include "../NodeKind.hpp" +#include "../NumberLeaf.hpp" +#include "../ObjectNode.hpp" +#include "../StringLeaf.hpp" +#include "custom_assert.hpp" + +int main() +{ + { + Node_ptr node = BooleanLeaf::make_ptr(true); + auto const& node_const_ref = *node; + ASSERT_UNEQUAL(node_const_ref.as_BooleanLeaf(), nullptr); + ASSERT_EQUAL(node_const_ref.as_NumberLeaf(), nullptr); + ASSERT_EQUAL(node_const_ref.as_StringLeaf(), nullptr); + ASSERT_EQUAL(node_const_ref.as_ArrayNode(), nullptr); + ASSERT_EQUAL(node_const_ref.as_ObjectNode(), nullptr); + } + + { + Node_ptr node = NumberLeaf::make_ptr(1); + auto const& node_const_ref = *node; + ASSERT_UNEQUAL(node_const_ref.as_NumberLeaf(), nullptr); + } + + { + Node_ptr node = StringLeaf::make_ptr("Hello world"); + auto const& node_const_ref = *node; + ASSERT_UNEQUAL(node_const_ref.as_StringLeaf(), nullptr); + } + + { + Node_ptr node = ArrayNode::make_ptr(); + auto const& node_const_ref = *node; + ASSERT_UNEQUAL(node_const_ref.as_ArrayNode(), nullptr); + } + + { + Node_ptr node = ObjectNode::make_ptr(); + auto const& node_const_ref = *node; + ASSERT_UNEQUAL(node_const_ref.as_ObjectNode(), nullptr); + } +} \ No newline at end of file diff --git a/tp-json/tests/42_navigation_object.cpp b/tp-json/tests/42_navigation_object.cpp new file mode 100644 index 00000000..3ab3377d --- /dev/null +++ b/tp-json/tests/42_navigation_object.cpp @@ -0,0 +1,18 @@ +#include "../JsonParser.hpp" +#include "custom_assert.hpp" + +int main() +{ + std::string json_dir = "json/"; + + std::string filename = json_dir + "pokedex.json"; + auto node = JsonParser::parse_from_file(filename); + const auto& const_node = node->as_ObjectNode(); + + ASSERT_TRUE(const_node->has_child("pokemon")); + ASSERT_FALSE(const_node->has_child("digimon")); + + auto pokemon_node = const_node->at("pokemon"); + ASSERT_UNEQUAL(pokemon_node, nullptr); + ASSERT_EQUAL(const_node->at("digimon"), nullptr); +} \ No newline at end of file diff --git a/tp-json/tests/43_navigation_array.cpp b/tp-json/tests/43_navigation_array.cpp new file mode 100644 index 00000000..617a0f6e --- /dev/null +++ b/tp-json/tests/43_navigation_array.cpp @@ -0,0 +1,16 @@ +#include "../JsonParser.hpp" +#include "custom_assert.hpp" + +int main() +{ + std::string json_dir = "json/"; + + std::string filename = json_dir + "pokedex.json"; + auto node = JsonParser::parse_from_file(filename); + auto pokemon_node = node->at("pokemon"); + ASSERT_EQUAL(pokemon_node->kind(), NodeKind::ARRAY); + ASSERT_EQUAL(pokemon_node->child_count(), 151u); + + /* Pikachu pokedex number is 25 */ + ASSERT_EQUAL(pokemon_node->at(24)->at("name")->as_StringLeaf()->data(), "Pikachu"); +} \ No newline at end of file diff --git a/tp-json/tests/44_navigation_full.cpp b/tp-json/tests/44_navigation_full.cpp new file mode 100644 index 00000000..a347929a --- /dev/null +++ b/tp-json/tests/44_navigation_full.cpp @@ -0,0 +1,88 @@ +#include "../JsonParser.hpp" +#include "custom_assert.hpp" + +#include +#include +#include +#include + +int main() +{ + std::string json_dir = "json/"; + + std::string filename = json_dir + "pokedex.json"; + Node_ptr node = JsonParser::parse_from_file(filename); + + ASSERT_EQUAL(node->kind(), NodeKind::OBJECT) + ASSERT_UNEQUAL(node->as_ObjectNode()->at("pokemon"), nullptr) + ASSERT_EQUAL(node->as_ObjectNode()->at("pokemon")->kind(), NodeKind::ARRAY) + + std::unordered_map pokemon_id_by_name; + for (const auto& pokemon_node : *(node->as_ObjectNode()->at("pokemon")->as_ArrayNode())) + { + ASSERT_EQUAL(pokemon_node->kind(), NodeKind::OBJECT) + auto pokemon_obj = pokemon_node->as_ObjectNode(); + + ASSERT_UNEQUAL(pokemon_obj->at("name"), nullptr) + ASSERT_EQUAL(pokemon_obj->at("name")->kind(), NodeKind::STRING) + const std::string& name = pokemon_obj->at("name")->as_StringLeaf()->data(); + + ASSERT_UNEQUAL(pokemon_obj->at("id"), nullptr) + ASSERT_UNEQUAL(pokemon_obj->at("id")->as_NumberLeaf(), nullptr) + unsigned id = pokemon_obj->at("id")->as_NumberLeaf()->data(); + + pokemon_id_by_name[name] = id; + } + + std::unordered_map> pokemon_id_by_type; + for (const auto& pokemon_node : *(node->as_ObjectNode()->at("pokemon")->as_ArrayNode())) + { + ASSERT_EQUAL(pokemon_node->kind(), NodeKind::OBJECT) + ASSERT_UNEQUAL(pokemon_node->as_ObjectNode()->at("type"), nullptr) + ASSERT_EQUAL(pokemon_node->as_ObjectNode()->at("type")->kind(), NodeKind::ARRAY) + for (const auto& type_node : *(pokemon_node->as_ObjectNode()->at("type")->as_ArrayNode())) + { + ASSERT_UNEQUAL(type_node->as_StringLeaf(), nullptr) + const std::string& type = type_node->as_StringLeaf()->data(); + auto it = pokemon_id_by_type.find(type); + if (it == pokemon_id_by_type.end()) + it = pokemon_id_by_type.emplace(type, std::vector()).first; + it->second.emplace_back(pokemon_node->as_ObjectNode()->at("id")->as_NumberLeaf()->data()); + } + } + + for (const auto& pair : pokemon_id_by_type) + { + std::cerr << pair.first << ": ["; + bool postfirst = false; + for (auto i : pair.second) + { + if (postfirst) + std::cerr << ", "; + else + postfirst = true; + std::cerr << i; + } + std::cerr << "]" << std::endl; + } + + std::vector> const tests = { { "Bulbasaur", "Grass" }, + { "Bulbasaur", "Poison" }, + { "Wartortle", "Water" }, + { "Pikachu", "Electric" } }; + for (auto const& [name, type] : tests) + { + std::cerr << "Checking that \"" << name << "\" is of type \"" << type << "\"." << std::endl; + + const auto& list_it = pokemon_id_by_type.find(type); + const auto& elt_it = pokemon_id_by_name.find(name); + + assert(list_it != pokemon_id_by_type.end()); + assert(elt_it != pokemon_id_by_name.end()); + + const std::vector& poke_list = list_it->second; + unsigned poke_elt = elt_it->second; + + assert(std::find(poke_list.begin(), poke_list.end(), poke_elt) != poke_list.end()); + } +} \ No newline at end of file diff --git a/tp-json/tests/50_modification.cpp b/tp-json/tests/50_modification.cpp new file mode 100644 index 00000000..120c3269 --- /dev/null +++ b/tp-json/tests/50_modification.cpp @@ -0,0 +1,24 @@ +#include "../JsonParser.hpp" +#include "custom_assert.hpp" + +int main() +{ + std::string json_dir = "json/"; + + std::string filename = json_dir + "pokedex.json"; + auto node = JsonParser::parse_from_file(filename); + auto pokemon_node = node->at("pokemon")->as_ArrayNode(); + + auto chikorita = ObjectNode::make_ptr(); + chikorita->add("id", NumberLeaf::make_ptr(152)); + chikorita->add("name", StringLeaf::make_ptr("Chikorita")); + chikorita->add("num", StringLeaf::make_ptr("152")); + chikorita->add("type", ArrayNode::make_ptr()); + chikorita->at("type")->as_ArrayNode()->add(StringLeaf::make_ptr("Grass")); + + pokemon_node->add(std::move(chikorita)); + + ASSERT_EQUAL(pokemon_node->child_count(), 152u); + ASSERT_EQUAL(node->height(), 5u); + ASSERT_EQUAL(node->node_count(), 3779u + 6u); +} \ No newline at end of file diff --git a/tp-json/tests/51_copy.cpp b/tp-json/tests/51_copy.cpp new file mode 100644 index 00000000..f7c6ca0e --- /dev/null +++ b/tp-json/tests/51_copy.cpp @@ -0,0 +1,27 @@ +#include "../JsonParser.hpp" +#include "custom_assert.hpp" + +int main() +{ + + std::string json_dir = "json/"; + std::string filename = json_dir + "pokedex.json"; + Node_ptr original = JsonParser::parse_from_file(filename); + + // We make the copy multiple times to ensure that deallocation works + for (unsigned i = 0; i < 100; ++i) + { + Node_ptr copy = original->deep_copy(); + ASSERT_EQUAL(copy->height(), 5u); + ASSERT_EQUAL(copy->node_count(), 3779u); + ASSERT_EQUAL(copy->kind(), NodeKind::OBJECT); + + ASSERT_EQUAL(*original, *copy); + + ASSERT_UNEQUAL(&*original, &*copy); + } + + // We browse the original tree to check that it was not deallocated. + ASSERT_EQUAL(original->height(), 5u); + ASSERT_EQUAL(original->node_count(), 3779u); +} \ No newline at end of file diff --git a/tp-json/tests/99_dot.cpp b/tp-json/tests/99_dot.cpp new file mode 100644 index 00000000..47460494 --- /dev/null +++ b/tp-json/tests/99_dot.cpp @@ -0,0 +1,28 @@ +#include "../JsonParser.hpp" +#include "custom_assert.hpp" + +#include +#include +#include +#include +#include + +void dot(std::ostream& o, Node_ptr const& node) +{ + o << "digraph G {" << std::endl; + o << "rankdir=LR;" << std::endl; + node->dot(o); + o << "}" << std::endl; +} + +int main() +{ + std::string dir = "json/"; + + std::string filename = dir + "cpp2022.json"; + std::cerr << "Starting test with: " << filename << std::endl; + Node_ptr node = JsonParser::parse_from_file(filename); + + std::ofstream out("/tmp/test.gv"); + dot(out, node); +} \ No newline at end of file diff --git a/tp-json/tests/CMakeLists.txt b/tp-json/tests/CMakeLists.txt new file mode 100644 index 00000000..da1fad61 --- /dev/null +++ b/tp-json/tests/CMakeLists.txt @@ -0,0 +1,26 @@ +file(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} [0-9]*.cpp) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/json DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + + +SET(OLDVAR 0) +SET(VAR 1) +FOREACH(TEST ${TESTS}) + MESSAGE(STATUS test:${TEST}) + add_executable(tp-json-test-${TEST} EXCLUDE_FROM_ALL ${TP-JSON-SOURCES} "${CMAKE_CURRENT_SOURCE_DIR}/${TEST}" ) + + add_test(NAME build:${TEST} COMMAND "${CMAKE_COMMAND}" --build ${CMAKE_BINARY_DIR} --target tp-json-test-${TEST}) + set_tests_properties(build:${TEST} PROPERTIES FIXTURES_SETUP ${VAR}) + IF(NOT VAR EQUAL 1) + set_tests_properties(build:${TEST} PROPERTIES FIXTURES_REQUIRED ${OLDVAR}) + ENDIF() + + SET(OLDVAR ${VAR}) + MATH(EXPR VAR "${VAR}+1") + + add_test(NAME run:${TEST} COMMAND ./tp-json-test-${TEST}) + set_tests_properties(run:${TEST} PROPERTIES FIXTURES_SETUP ${VAR}) + set_tests_properties(run:${TEST} PROPERTIES FIXTURES_REQUIRED ${OLDVAR}) + + SET(OLDVAR ${VAR}) + MATH(EXPR VAR "${VAR}+1") +ENDFOREACH() diff --git a/tp-json/tests/custom_assert.hpp b/tp-json/tests/custom_assert.hpp new file mode 100644 index 00000000..79a381c4 --- /dev/null +++ b/tp-json/tests/custom_assert.hpp @@ -0,0 +1,46 @@ +#include +#include + +template +void assert_equal(const T1& left, const T2& right, unsigned line) +{ + if (!(left == right)) + { + std::cerr << "On line " << line << ", assert_equal failed the following tests returns false: " << left + << " == " << right << std::endl; + exit(EXIT_FAILURE); + } +} +#define ASSERT_EQUAL(x, y) assert_equal(x, y, __LINE__); + +template +void assert_unequal(const T1& left, const T2& right, unsigned line) +{ + if (!(left != right)) + { + std::cerr << "On line " << line + << ", assert_unequal failed the following tests returns false: " << left << " != " << right + << std::endl; + exit(EXIT_FAILURE); + } +} +#define ASSERT_UNEQUAL(x, y) assert_unequal(x, y, __LINE__); + +inline void assert_true(bool b, unsigned line) +{ + if (b) + return; + std::cerr << "On line " << line << ", assert_true failed" << std::endl; + exit(EXIT_FAILURE); +} + +#define ASSERT_TRUE(b) assert_true(b, __LINE__); + +inline void assert_false(bool b, unsigned line) +{ + if (!b) + return; + std::cerr << "On line " << line << ", assert_false failed" << std::endl; + exit(EXIT_FAILURE); +} +#define ASSERT_FALSE(b) assert_false(b, __LINE__); diff --git a/tp-json/tests/json/array_empty.json b/tp-json/tests/json/array_empty.json new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/tp-json/tests/json/array_empty.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tp-json/tests/json/array_hexadecimal.json b/tp-json/tests/json/array_hexadecimal.json new file mode 100644 index 00000000..244cc45a --- /dev/null +++ b/tp-json/tests/json/array_hexadecimal.json @@ -0,0 +1 @@ +[[[[0,1],[2,3]],[[4,5],[6,7]]],[[[8,9],["A","B"]],[["C","D"],["E","F"]]]] \ No newline at end of file diff --git a/tp-json/tests/json/array_range10.json b/tp-json/tests/json/array_range10.json new file mode 100644 index 00000000..0b5c0682 --- /dev/null +++ b/tp-json/tests/json/array_range10.json @@ -0,0 +1 @@ +[0,1,2,3,4,5,6,7,8,9] \ No newline at end of file diff --git a/tp-json/tests/json/boolean_false.json b/tp-json/tests/json/boolean_false.json new file mode 100644 index 00000000..02e4a84d --- /dev/null +++ b/tp-json/tests/json/boolean_false.json @@ -0,0 +1 @@ +false \ No newline at end of file diff --git a/tp-json/tests/json/boolean_true.json b/tp-json/tests/json/boolean_true.json new file mode 100644 index 00000000..f32a5804 --- /dev/null +++ b/tp-json/tests/json/boolean_true.json @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/tp-json/tests/json/cpp2022.json b/tp-json/tests/json/cpp2022.json new file mode 100644 index 00000000..4c42bc4b --- /dev/null +++ b/tp-json/tests/json/cpp2022.json @@ -0,0 +1,14 @@ +{ + "name":"C++", + "level":"M1", + "mandatory":true, + "teachers":["Céline","Matthias","Victor"], + "year": 2022, + "sessions":[ + {"kind":"TP", "number":1, "week":1}, + {"kind":"TP", "number":2, "week":2}, + {"kind":"TP", "number":3, "week":3}, + {"kind":"TP", "number":4, "week":4}, + {"kind":"Project", "weeks":[5,6,7,8,9,10,11,12]} + ] +} diff --git a/tp-json/tests/json/number_42.json b/tp-json/tests/json/number_42.json new file mode 100644 index 00000000..f70d7bba --- /dev/null +++ b/tp-json/tests/json/number_42.json @@ -0,0 +1 @@ +42 \ No newline at end of file diff --git a/tp-json/tests/json/object_alphabet.json b/tp-json/tests/json/object_alphabet.json new file mode 100644 index 00000000..4f90682e --- /dev/null +++ b/tp-json/tests/json/object_alphabet.json @@ -0,0 +1 @@ +{"a":97,"b":98,"c":99,"d":100,"e":101,"f":102,"g":103,"h":104,"i":105,"j":106,"k":107,"l":108,"m":109,"n":110,"o":111,"p":112,"q":113,"r":114,"s":115,"t":116,"u":117,"v":118,"w":119,"x":120,"y":121,"z":122} \ No newline at end of file diff --git a/tp-json/tests/json/object_empty.json b/tp-json/tests/json/object_empty.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/tp-json/tests/json/object_empty.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/tp-json/tests/json/pokedex.json b/tp-json/tests/json/pokedex.json new file mode 100644 index 00000000..b9ec6bc9 --- /dev/null +++ b/tp-json/tests/json/pokedex.json @@ -0,0 +1,4086 @@ +{ + "pokemon": [{ + "id": 1, + "num": "001", + "name": "Bulbasaur", + "img": "http://www.serebii.net/pokemongo/pokemon/001.png", + "type": [ + "Grass", + "Poison" + ], + "height": "0.71 m", + "weight": "6.9 kg", + "candy": "Bulbasaur Candy", + "candy_count": 25, + "egg": "2 km", + "spawn_chance": 0.69, + "avg_spawns": 69, + "spawn_time": "20:00", + "multipliers": [1.58], + "weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "next_evolution": [{ + "num": "002", + "name": "Ivysaur" + }, { + "num": "003", + "name": "Venusaur" + }] + }, { + "id": 2, + "num": "002", + "name": "Ivysaur", + "img": "http://www.serebii.net/pokemongo/pokemon/002.png", + "type": [ + "Grass", + "Poison" + ], + "height": "0.99 m", + "weight": "13.0 kg", + "candy": "Bulbasaur Candy", + "candy_count": 100, + "egg": "Not in Eggs", + "spawn_chance": 0.042, + "avg_spawns": 4.2, + "spawn_time": "07:00", + "multipliers": [ + 1.2, + 1.6 + ], + "weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "prev_evolution": [{ + "num": "001", + "name": "Bulbasaur" + }], + "next_evolution": [{ + "num": "003", + "name": "Venusaur" + }] + }, { + "id": 3, + "num": "003", + "name": "Venusaur", + "img": "http://www.serebii.net/pokemongo/pokemon/003.png", + "type": [ + "Grass", + "Poison" + ], + "height": "2.01 m", + "weight": "100.0 kg", + "candy": "Bulbasaur Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.017, + "avg_spawns": 1.7, + "spawn_time": "11:30", + "multipliers": [], + "weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "prev_evolution": [{ + "num": "001", + "name": "Bulbasaur" + }, { + "num": "002", + "name": "Ivysaur" + }] + }, { + "id": 4, + "num": "004", + "name": "Charmander", + "img": "http://www.serebii.net/pokemongo/pokemon/004.png", + "type": [ + "Fire" + ], + "height": "0.61 m", + "weight": "8.5 kg", + "candy": "Charmander Candy", + "candy_count": 25, + "egg": "2 km", + "spawn_chance": 0.253, + "avg_spawns": 25.3, + "spawn_time": "08:45", + "multipliers": [1.65], + "weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "next_evolution": [{ + "num": "005", + "name": "Charmeleon" + }, { + "num": "006", + "name": "Charizard" + }] + }, { + "id": 5, + "num": "005", + "name": "Charmeleon", + "img": "http://www.serebii.net/pokemongo/pokemon/005.png", + "type": [ + "Fire" + ], + "height": "1.09 m", + "weight": "19.0 kg", + "candy": "Charmander Candy", + "candy_count": 100, + "egg": "Not in Eggs", + "spawn_chance": 0.012, + "avg_spawns": 1.2, + "spawn_time": "19:00", + "multipliers": [1.79], + "weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "prev_evolution": [{ + "num": "004", + "name": "Charmander" + }], + "next_evolution": [{ + "num": "006", + "name": "Charizard" + }] + }, { + "id": 6, + "num": "006", + "name": "Charizard", + "img": "http://www.serebii.net/pokemongo/pokemon/006.png", + "type": [ + "Fire", + "Flying" + ], + "height": "1.70 m", + "weight": "90.5 kg", + "candy": "Charmander Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.0031, + "avg_spawns": 0.31, + "spawn_time": "13:34", + "multipliers": [], + "weaknesses": [ + "Water", + "Electric", + "Rock" + ], + "prev_evolution": [{ + "num": "004", + "name": "Charmander" + }, { + "num": "005", + "name": "Charmeleon" + }] + }, { + "id": 7, + "num": "007", + "name": "Squirtle", + "img": "http://www.serebii.net/pokemongo/pokemon/007.png", + "type": [ + "Water" + ], + "height": "0.51 m", + "weight": "9.0 kg", + "candy": "Squirtle Candy", + "candy_count": 25, + "egg": "2 km", + "spawn_chance": 0.58, + "avg_spawns": 58, + "spawn_time": "04:25", + "multipliers": [2.1], + "weaknesses": [ + "Electric", + "Grass" + ], + "next_evolution": [{ + "num": "008", + "name": "Wartortle" + }, { + "num": "009", + "name": "Blastoise" + }] + }, { + "id": 8, + "num": "008", + "name": "Wartortle", + "img": "http://www.serebii.net/pokemongo/pokemon/008.png", + "type": [ + "Water" + ], + "height": "0.99 m", + "weight": "22.5 kg", + "candy": "Squirtle Candy", + "candy_count": 100, + "egg": "Not in Eggs", + "spawn_chance": 0.034, + "avg_spawns": 3.4, + "spawn_time": "07:02", + "multipliers": [1.4], + "weaknesses": [ + "Electric", + "Grass" + ], + "prev_evolution": [{ + "num": "007", + "name": "Squirtle" + }], + "next_evolution": [{ + "num": "009", + "name": "Blastoise" + }] + }, { + "id": 9, + "num": "009", + "name": "Blastoise", + "img": "http://www.serebii.net/pokemongo/pokemon/009.png", + "type": [ + "Water" + ], + "height": "1.60 m", + "weight": "85.5 kg", + "candy": "Squirtle Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.0067, + "avg_spawns": 0.67, + "spawn_time": "00:06", + "multipliers": [], + "weaknesses": [ + "Electric", + "Grass" + ], + "prev_evolution": [{ + "num": "007", + "name": "Squirtle" + }, { + "num": "008", + "name": "Wartortle" + }] + }, { + "id": 10, + "num": "010", + "name": "Caterpie", + "img": "http://www.serebii.net/pokemongo/pokemon/010.png", + "type": [ + "Bug" + ], + "height": "0.30 m", + "weight": "2.9 kg", + "candy": "Caterpie Candy", + "candy_count": 12, + "egg": "2 km", + "spawn_chance": 3.032, + "avg_spawns": 303.2, + "spawn_time": "16:35", + "multipliers": [1.05], + "weaknesses": [ + "Fire", + "Flying", + "Rock" + ], + "next_evolution": [{ + "num": "011", + "name": "Metapod" + }, { + "num": "012", + "name": "Butterfree" + }] + }, { + "id": 11, + "num": "011", + "name": "Metapod", + "img": "http://www.serebii.net/pokemongo/pokemon/011.png", + "type": [ + "Bug" + ], + "height": "0.71 m", + "weight": "9.9 kg", + "candy": "Caterpie Candy", + "candy_count": 50, + "egg": "Not in Eggs", + "spawn_chance": 0.187, + "avg_spawns": 18.7, + "spawn_time": "02:11", + "multipliers": [ + 3.55, + 3.79 + ], + "weaknesses": [ + "Fire", + "Flying", + "Rock" + ], + "prev_evolution": [{ + "num": "010", + "name": "Caterpie" + }], + "next_evolution": [{ + "num": "012", + "name": "Butterfree" + }] + }, { + "id": 12, + "num": "012", + "name": "Butterfree", + "img": "http://www.serebii.net/pokemongo/pokemon/012.png", + "type": [ + "Bug", + "Flying" + ], + "height": "1.09 m", + "weight": "32.0 kg", + "candy": "Caterpie Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.022, + "avg_spawns": 2.2, + "spawn_time": "05:23", + "multipliers": [], + "weaknesses": [ + "Fire", + "Electric", + "Ice", + "Flying", + "Rock" + ], + "prev_evolution": [{ + "num": "010", + "name": "Caterpie" + }, { + "num": "011", + "name": "Metapod" + }] + }, { + "id": 13, + "num": "013", + "name": "Weedle", + "img": "http://www.serebii.net/pokemongo/pokemon/013.png", + "type": [ + "Bug", + "Poison" + ], + "height": "0.30 m", + "weight": "3.2 kg", + "candy": "Weedle Candy", + "candy_count": 12, + "egg": "2 km", + "spawn_chance": 7.12, + "avg_spawns": 712, + "spawn_time": "02:21", + "multipliers": [ + 1.01, + 1.09 + ], + "weaknesses": [ + "Fire", + "Flying", + "Psychic", + "Rock" + ], + "next_evolution": [{ + "num": "014", + "name": "Kakuna" + }, { + "num": "015", + "name": "Beedrill" + }] + }, { + "id": 14, + "num": "014", + "name": "Kakuna", + "img": "http://www.serebii.net/pokemongo/pokemon/014.png", + "type": [ + "Bug", + "Poison" + ], + "height": "0.61 m", + "weight": "10.0 kg", + "candy": "Weedle Candy", + "candy_count": 50, + "egg": "Not in Eggs", + "spawn_chance": 0.44, + "avg_spawns": 44, + "spawn_time": "02:30", + "multipliers": [ + 3.01, + 3.41 + ], + "weaknesses": [ + "Fire", + "Flying", + "Psychic", + "Rock" + ], + "prev_evolution": [{ + "num": "013", + "name": "Weedle" + }], + "next_evolution": [{ + "num": "015", + "name": "Beedrill" + }] + }, { + "id": 15, + "num": "015", + "name": "Beedrill", + "img": "http://www.serebii.net/pokemongo/pokemon/015.png", + "type": [ + "Bug", + "Poison" + ], + "height": "0.99 m", + "weight": "29.5 kg", + "candy": "Weedle Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.051, + "avg_spawns": 5.1, + "spawn_time": "04:50", + "multipliers": [], + "weaknesses": [ + "Fire", + "Flying", + "Psychic", + "Rock" + ], + "prev_evolution": [{ + "num": "013", + "name": "Weedle" + }, { + "num": "014", + "name": "Kakuna" + }] + }, { + "id": 16, + "num": "016", + "name": "Pidgey", + "img": "http://www.serebii.net/pokemongo/pokemon/016.png", + "type": [ + "Normal", + "Flying" + ], + "height": "0.30 m", + "weight": "1.8 kg", + "candy": "Pidgey Candy", + "candy_count": 12, + "egg": "2 km", + "spawn_chance": 15.98, + "avg_spawns": 1.598, + "spawn_time": "01:34", + "multipliers": [ + 1.71, + 1.92 + ], + "weaknesses": [ + "Electric", + "Rock" + ], + "next_evolution": [{ + "num": "017", + "name": "Pidgeotto" + }, { + "num": "018", + "name": "Pidgeot" + }] + }, { + "id": 17, + "num": "017", + "name": "Pidgeotto", + "img": "http://www.serebii.net/pokemongo/pokemon/017.png", + "type": [ + "Normal", + "Flying" + ], + "height": "1.09 m", + "weight": "30.0 kg", + "candy": "Pidgey Candy", + "candy_count": 50, + "egg": "Not in Eggs", + "spawn_chance": 1.02, + "avg_spawns": 102, + "spawn_time": "01:30", + "multipliers": [1.79], + "weaknesses": [ + "Electric", + "Rock" + ], + "prev_evolution": [{ + "num": "016", + "name": "Pidgey" + }], + "next_evolution": [{ + "num": "018", + "name": "Pidgeot" + }] + }, { + "id": 18, + "num": "018", + "name": "Pidgeot", + "img": "http://www.serebii.net/pokemongo/pokemon/018.png", + "type": [ + "Normal", + "Flying" + ], + "height": "1.50 m", + "weight": "39.5 kg", + "candy": "Pidgey Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.13, + "avg_spawns": 13, + "spawn_time": "01:50", + "multipliers": [], + "weaknesses": [ + "Electric", + "Rock" + ], + "prev_evolution": [{ + "num": "016", + "name": "Pidgey" + }, { + "num": "017", + "name": "Pidgeotto" + }] + }, { + "id": 19, + "num": "019", + "name": "Rattata", + "img": "http://www.serebii.net/pokemongo/pokemon/019.png", + "type": [ + "Normal" + ], + "height": "0.30 m", + "weight": "3.5 kg", + "candy": "Rattata Candy", + "candy_count": 25, + "egg": "2 km", + "spawn_chance": 13.05, + "avg_spawns": 1.305, + "spawn_time": "01:55", + "multipliers": [ + 2.55, + 2.73 + ], + "weaknesses": [ + "Fighting" + ], + "next_evolution": [{ + "num": "020", + "name": "Raticate" + }] + }, { + "id": 20, + "num": "020", + "name": "Raticate", + "img": "http://www.serebii.net/pokemongo/pokemon/020.png", + "type": [ + "Normal" + ], + "height": "0.71 m", + "weight": "18.5 kg", + "candy": "Rattata Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.41, + "avg_spawns": 41, + "spawn_time": "01:56", + "multipliers": [], + "weaknesses": [ + "Fighting" + ], + "prev_evolution": [{ + "num": "019", + "name": "Rattata" + }] + }, { + "id": 21, + "num": "021", + "name": "Spearow", + "img": "http://www.serebii.net/pokemongo/pokemon/021.png", + "type": [ + "Normal", + "Flying" + ], + "height": "0.30 m", + "weight": "2.0 kg", + "candy": "Spearow Candy", + "candy_count": 50, + "egg": "2 km", + "spawn_chance": 4.73, + "avg_spawns": 473, + "spawn_time": "12:25", + "multipliers": [ + 2.66, + 2.68 + ], + "weaknesses": [ + "Electric", + "Rock" + ], + "next_evolution": [{ + "num": "022", + "name": "Fearow" + }] + }, { + "id": 22, + "num": "022", + "name": "Fearow", + "img": "http://www.serebii.net/pokemongo/pokemon/022.png", + "type": [ + "Normal", + "Flying" + ], + "height": "1.19 m", + "weight": "38.0 kg", + "candy": "Spearow Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.15, + "avg_spawns": 15, + "spawn_time": "01:11", + "multipliers": [], + "weaknesses": [ + "Electric", + "Rock" + ], + "prev_evolution": [{ + "num": "021", + "name": "Spearow" + }] + }, { + "id": 23, + "num": "023", + "name": "Ekans", + "img": "http://www.serebii.net/pokemongo/pokemon/023.png", + "type": [ + "Poison" + ], + "height": "2.01 m", + "weight": "6.9 kg", + "candy": "Ekans Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 2.27, + "avg_spawns": 227, + "spawn_time": "12:20", + "multipliers": [ + 2.21, + 2.27 + ], + "weaknesses": [ + "Ground", + "Psychic" + ], + "next_evolution": [{ + "num": "024", + "name": "Arbok" + }] + }, { + "id": 24, + "num": "024", + "name": "Arbok", + "img": "http://www.serebii.net/pokemongo/pokemon/024.png", + "type": [ + "Poison" + ], + "height": "3.51 m", + "weight": "65.0 kg", + "candy": "Ekans Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.072, + "avg_spawns": 7.2, + "spawn_time": "01:50", + "multipliers": [], + "weaknesses": [ + "Ground", + "Psychic" + ], + "prev_evolution": [{ + "num": "023", + "name": "Ekans" + }] + }, { + "id": 25, + "num": "025", + "name": "Pikachu", + "img": "http://www.serebii.net/pokemongo/pokemon/025.png", + "type": [ + "Electric" + ], + "height": "0.41 m", + "weight": "6.0 kg", + "candy": "Pikachu Candy", + "candy_count": 50, + "egg": "2 km", + "spawn_chance": 0.21, + "avg_spawns": 21, + "spawn_time": "04:00", + "multipliers": [2.34], + "weaknesses": [ + "Ground" + ], + "next_evolution": [{ + "num": "026", + "name": "Raichu" + }] + }, { + "id": 26, + "num": "026", + "name": "Raichu", + "img": "http://www.serebii.net/pokemongo/pokemon/026.png", + "type": [ + "Electric" + ], + "height": "0.79 m", + "weight": "30.0 kg", + "candy": "Pikachu Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.0076, + "avg_spawns": 0.76, + "spawn_time": "23:58", + "multipliers": [], + "weaknesses": [ + "Ground" + ], + "prev_evolution": [{ + "num": "025", + "name": "Pikachu" + }] + }, { + "id": 27, + "num": "027", + "name": "Sandshrew", + "img": "http://www.serebii.net/pokemongo/pokemon/027.png", + "type": [ + "Ground" + ], + "height": "0.61 m", + "weight": "12.0 kg", + "candy": "Sandshrew Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 1.11, + "avg_spawns": 111, + "spawn_time": "01:58", + "multipliers": [2.45], + "weaknesses": [ + "Water", + "Grass", + "Ice" + ], + "next_evolution": [{ + "num": "028", + "name": "Sandslash" + }] + }, { + "id": 28, + "num": "028", + "name": "Sandslash", + "img": "http://www.serebii.net/pokemongo/pokemon/028.png", + "type": [ + "Ground" + ], + "height": "0.99 m", + "weight": "29.5 kg", + "candy": "Sandshrew Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.037, + "avg_spawns": 3.7, + "spawn_time": "12:34", + "multipliers": [], + "weaknesses": [ + "Water", + "Grass", + "Ice" + ], + "prev_evolution": [{ + "num": "027", + "name": "Sandshrew" + }] + }, { + "id": 29, + "num": "029", + "name": "Nidoran ♀ (Female)", + "img": "http://www.serebii.net/pokemongo/pokemon/029.png", + "type": [ + "Poison" + ], + "height": "0.41 m", + "weight": "7.0 kg", + "candy": "Nidoran ♀ (Female) Candy", + "candy_count": 25, + "egg": "5 km", + "spawn_chance": 1.38, + "avg_spawns": 138, + "spawn_time": "01:51", + "multipliers": [ + 1.63, + 2.48 + ], + "weaknesses": [ + "Ground", + "Psychic" + ], + "next_evolution": [{ + "num": "030", + "name": "Nidorina" + }, { + "num": "031", + "name": "Nidoqueen" + }] + }, { + "id": 30, + "num": "030", + "name": "Nidorina", + "img": "http://www.serebii.net/pokemongo/pokemon/030.png", + "type": [ + "Poison" + ], + "height": "0.79 m", + "weight": "20.0 kg", + "candy": "Nidoran ♀ (Female) Candy", + "candy_count": 100, + "egg": "Not in Eggs", + "spawn_chance": 0.088, + "avg_spawns": 8.8, + "spawn_time": "07:22", + "multipliers": [ + 1.83, + 2.48 + ], + "weaknesses": [ + "Ground", + "Psychic" + ], + "prev_evolution": [{ + "num": "029", + "name": "Nidoran(Female)" + }], + "next_evolution": [{ + "num": "031", + "name": "Nidoqueen" + }] + }, { + "id": 31, + "num": "031", + "name": "Nidoqueen", + "img": "http://www.serebii.net/pokemongo/pokemon/031.png", + "type": [ + "Poison", + "Ground" + ], + "height": "1.30 m", + "weight": "60.0 kg", + "candy": "Nidoran ♀ (Female) Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.012, + "avg_spawns": 1.2, + "spawn_time": "12:35", + "multipliers": [], + "weaknesses": [ + "Water", + "Ice", + "Ground", + "Psychic" + ], + "prev_evolution": [{ + "num": "029", + "name": "Nidoran(Female)" + }, { + "num": "030", + "name": "Nidorina" + }] + }, { + "id": 32, + "num": "032", + "name": "Nidoran ♂ (Male)", + "img": "http://www.serebii.net/pokemongo/pokemon/032.png", + "type": [ + "Poison" + ], + "height": "0.51 m", + "weight": "9.0 kg", + "candy": "Nidoran ♂ (Male) Candy", + "candy_count": 25, + "egg": "5 km", + "spawn_chance": 1.31, + "avg_spawns": 131, + "spawn_time": "01:12", + "multipliers": [ + 1.64, + 1.7 + ], + "weaknesses": [ + "Ground", + "Psychic" + ], + "next_evolution": [{ + "num": "033", + "name": "Nidorino" + }, { + "num": "034", + "name": "Nidoking" + }] + }, { + "id": 33, + "num": "033", + "name": "Nidorino", + "img": "http://www.serebii.net/pokemongo/pokemon/033.png", + "type": [ + "Poison" + ], + "height": "0.89 m", + "weight": "19.5 kg", + "candy": "Nidoran ♂ (Male) Candy", + "candy_count": 100, + "egg": "Not in Eggs", + "spawn_chance": 0.083, + "avg_spawns": 8.3, + "spawn_time": "09:02", + "multipliers": [1.83], + "weaknesses": [ + "Ground", + "Psychic" + ], + "prev_evolution": [{ + "num": "032", + "name": "Nidoran(Male)" + }], + "next_evolution": [{ + "num": "034", + "name": "Nidoking" + }] + }, { + "id": 34, + "num": "034", + "name": "Nidoking", + "img": "http://www.serebii.net/pokemongo/pokemon/034.png", + "type": [ + "Poison", + "Ground" + ], + "height": "1.40 m", + "weight": "62.0 kg", + "candy": "Nidoran ♂ (Male) Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.017, + "avg_spawns": 1.7, + "spawn_time": "12:16", + "multipliers": [], + "weaknesses": [ + "Water", + "Ice", + "Ground", + "Psychic" + ], + "prev_evolution": [{ + "num": "032", + "name": "Nidoran(Male)" + }, { + "num": "033", + "name": "Nidorino" + }] + }, { + "id": 35, + "num": "035", + "name": "Clefairy", + "img": "http://www.serebii.net/pokemongo/pokemon/035.png", + "type": [ + "Normal" + ], + "height": "0.61 m", + "weight": "7.5 kg", + "candy": "Clefairy Candy", + "candy_count": 50, + "egg": "2 km", + "spawn_chance": 0.92, + "avg_spawns": 92, + "spawn_time": "03:30", + "multipliers": [ + 2.03, + 2.14 + ], + "weaknesses": [ + "Fighting" + ], + "next_evolution": [{ + "num": "036", + "name": "Clefable" + }] + }, { + "id": 36, + "num": "036", + "name": "Clefable", + "img": "http://www.serebii.net/pokemongo/pokemon/036.png", + "type": [ + "Normal" + ], + "height": "1.30 m", + "weight": "40.0 kg", + "candy": "Clefairy Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.012, + "avg_spawns": 1.2, + "spawn_time": "03:29", + "multipliers": [], + "weaknesses": [ + "Fighting" + ], + "prev_evolution": [{ + "num": "035", + "name": "Clefairy" + }] + }, { + "id": 37, + "num": "037", + "name": "Vulpix", + "img": "http://www.serebii.net/pokemongo/pokemon/037.png", + "type": [ + "Fire" + ], + "height": "0.61 m", + "weight": "9.9 kg", + "candy": "Vulpix Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 0.22, + "avg_spawns": 22, + "spawn_time": "13:43", + "multipliers": [ + 2.74, + 2.81 + ], + "weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "next_evolution": [{ + "num": "038", + "name": "Ninetales" + }] + }, { + "id": 38, + "num": "038", + "name": "Ninetales", + "img": "http://www.serebii.net/pokemongo/pokemon/038.png", + "type": [ + "Fire" + ], + "height": "1.09 m", + "weight": "19.9 kg", + "candy": "Vulpix Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.0077, + "avg_spawns": 0.77, + "spawn_time": "01:32", + "multipliers": [], + "weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "prev_evolution": [{ + "num": "037", + "name": "Vulpix" + }] + }, { + "id": 39, + "num": "039", + "name": "Jigglypuff", + "img": "http://www.serebii.net/pokemongo/pokemon/039.png", + "type": [ + "Normal" + ], + "height": "0.51 m", + "weight": "5.5 kg", + "candy": "Jigglypuff Candy", + "candy_count": 50, + "egg": "2 km", + "spawn_chance": 0.39, + "avg_spawns": 39, + "spawn_time": "08:46", + "multipliers": [1.85], + "weaknesses": [ + "Fighting" + ], + "next_evolution": [{ + "num": "040", + "name": "Wigglytuff" + }] + }, { + "id": 40, + "num": "040", + "name": "Wigglytuff", + "img": "http://www.serebii.net/pokemongo/pokemon/040.png", + "type": [ + "Normal" + ], + "height": "0.99 m", + "weight": "12.0 kg", + "candy": "Jigglypuff Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.018, + "avg_spawns": 1.8, + "spawn_time": "12:28", + "multipliers": [], + "weaknesses": [ + "Fighting" + ], + "prev_evolution": [{ + "num": "039", + "name": "Jigglypuff" + }] + }, { + "id": 41, + "num": "041", + "name": "Zubat", + "img": "http://www.serebii.net/pokemongo/pokemon/041.png", + "type": [ + "Poison", + "Flying" + ], + "height": "0.79 m", + "weight": "7.5 kg", + "candy": "Zubat Candy", + "candy_count": 50, + "egg": "2 km", + "spawn_chance": 6.52, + "avg_spawns": 652, + "spawn_time": "12:28", + "multipliers": [ + 2.6, + 3.67 + ], + "weaknesses": [ + "Electric", + "Ice", + "Psychic", + "Rock" + ], + "next_evolution": [{ + "num": "042", + "name": "Golbat" + }] + }, { + "id": 42, + "num": "042", + "name": "Golbat", + "img": "http://www.serebii.net/pokemongo/pokemon/042.png", + "type": [ + "Poison", + "Flying" + ], + "height": "1.60 m", + "weight": "55.0 kg", + "candy": "Zubat Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.42, + "avg_spawns": 42, + "spawn_time": "02:15", + "multipliers": [], + "weaknesses": [ + "Electric", + "Ice", + "Psychic", + "Rock" + ], + "prev_evolution": [{ + "num": "041", + "name": "Zubat" + }] + }, { + "id": 43, + "num": "043", + "name": "Oddish", + "img": "http://www.serebii.net/pokemongo/pokemon/043.png", + "type": [ + "Grass", + "Poison" + ], + "height": "0.51 m", + "weight": "5.4 kg", + "candy": "Oddish Candy", + "candy_count": 25, + "egg": "5 km", + "spawn_chance": 1.02, + "avg_spawns": 102, + "spawn_time": "03:58", + "multipliers": [1.5], + "weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "next_evolution": [{ + "num": "044", + "name": "Gloom" + }, { + "num": "045", + "name": "Vileplume" + }] + }, { + "id": 44, + "num": "044", + "name": "Gloom", + "img": "http://www.serebii.net/pokemongo/pokemon/044.png", + "type": [ + "Grass", + "Poison" + ], + "height": "0.79 m", + "weight": "8.6 kg", + "candy": "Oddish Candy", + "candy_count": 100, + "egg": "Not in Eggs", + "spawn_chance": 0.064, + "avg_spawns": 6.4, + "spawn_time": "11:33", + "multipliers": [1.49], + "weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "prev_evolution": [{ + "num": "043", + "name": "Oddish" + }], + "next_evolution": [{ + "num": "045", + "name": "Vileplume" + }] + }, { + "id": 45, + "num": "045", + "name": "Vileplume", + "img": "http://www.serebii.net/pokemongo/pokemon/045.png", + "type": [ + "Grass", + "Poison" + ], + "height": "1.19 m", + "weight": "18.6 kg", + "candy": "Oddish Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.0097, + "avg_spawns": 0.97, + "spawn_time": "23:58", + "multipliers": [], + "weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "prev_evolution": [{ + "num": "043", + "name": "Oddish" + }, { + "num": "044", + "name": "Gloom" + }] + }, { + "id": 46, + "num": "046", + "name": "Paras", + "img": "http://www.serebii.net/pokemongo/pokemon/046.png", + "type": [ + "Bug", + "Grass" + ], + "height": "0.30 m", + "weight": "5.4 kg", + "candy": "Paras Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 2.36, + "avg_spawns": 236, + "spawn_time": "01:42", + "multipliers": [2.02], + "weaknesses": [ + "Fire", + "Ice", + "Poison", + "Flying", + "Bug", + "Rock" + ], + "next_evolution": [{ + "num": "047", + "name": "Parasect" + }] + }, { + "id": 47, + "num": "047", + "name": "Parasect", + "img": "http://www.serebii.net/pokemongo/pokemon/047.png", + "type": [ + "Bug", + "Grass" + ], + "height": "0.99 m", + "weight": "29.5 kg", + "candy": "Paras Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.074, + "avg_spawns": 7.4, + "spawn_time": "01:22", + "multipliers": [], + "weaknesses": [ + "Fire", + "Ice", + "Poison", + "Flying", + "Bug", + "Rock" + ], + "prev_evolution": [{ + "num": "046", + "name": "Paras" + }] + }, { + "id": 48, + "num": "048", + "name": "Venonat", + "img": "http://www.serebii.net/pokemongo/pokemon/048.png", + "type": [ + "Bug", + "Poison" + ], + "height": "0.99 m", + "weight": "30.0 kg", + "candy": "Venonat Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 2.28, + "avg_spawns": 228, + "spawn_time": "02:31", + "multipliers": [ + 1.86, + 1.9 + ], + "weaknesses": [ + "Fire", + "Flying", + "Psychic", + "Rock" + ], + "next_evolution": [{ + "num": "049", + "name": "Venomoth" + }] + }, { + "id": 49, + "num": "049", + "name": "Venomoth", + "img": "http://www.serebii.net/pokemongo/pokemon/049.png", + "type": [ + "Bug", + "Poison" + ], + "height": "1.50 m", + "weight": "12.5 kg", + "candy": "Venonat Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.072, + "avg_spawns": 7.2, + "spawn_time": "23:40", + "multipliers": [], + "weaknesses": [ + "Fire", + "Flying", + "Psychic", + "Rock" + ], + "prev_evolution": [{ + "num": "048", + "name": "Venonat" + }] + }, { + "id": 50, + "num": "050", + "name": "Diglett", + "img": "http://www.serebii.net/pokemongo/pokemon/050.png", + "type": [ + "Ground" + ], + "height": "0.20 m", + "weight": "0.8 kg", + "candy": "Diglett Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 0.40, + "avg_spawns": 40, + "spawn_time": "02:22", + "multipliers": [2.69], + "weaknesses": [ + "Water", + "Grass", + "Ice" + ], + "next_evolution": [{ + "num": "051", + "name": "Dugtrio" + }] + }, { + "id": 51, + "num": "051", + "name": "Dugtrio", + "img": "http://www.serebii.net/pokemongo/pokemon/051.png", + "type": [ + "Ground" + ], + "height": "0.71 m", + "weight": "33.3 kg", + "candy": "Dugtrio", + "egg": "Not in Eggs", + "spawn_chance": 0.014, + "avg_spawns": 1.4, + "spawn_time": "12:37", + "multipliers": [], + "weaknesses": [ + "Water", + "Grass", + "Ice" + ], + "prev_evolution": [{ + "num": "050", + "name": "Diglett" + }] + }, { + "id": 52, + "num": "052", + "name": "Meowth", + "img": "http://www.serebii.net/pokemongo/pokemon/052.png", + "type": [ + "Normal" + ], + "height": "0.41 m", + "weight": "4.2 kg", + "candy": "Meowth Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 0.86, + "avg_spawns": 86, + "spawn_time": "02:54", + "multipliers": [1.98], + "weaknesses": [ + "Fighting" + ], + "next_evolution": [{ + "num": "053", + "name": "Persian" + }] + }, { + "id": 53, + "num": "053", + "name": "Persian", + "img": "http://www.serebii.net/pokemongo/pokemon/053.png", + "type": [ + "Normal" + ], + "height": "0.99 m", + "weight": "32.0 kg", + "candy": "Meowth Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.022, + "avg_spawns": 2.2, + "spawn_time": "02:44", + "multipliers": [], + "weaknesses": [ + "Fighting" + ], + "prev_evolution": [{ + "num": "052", + "name": "Meowth" + }] + }, { + "id": 54, + "num": "054", + "name": "Psyduck", + "img": "http://www.serebii.net/pokemongo/pokemon/054.png", + "type": [ + "Water" + ], + "height": "0.79 m", + "weight": "19.6 kg", + "candy": "Psyduck Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 2.54, + "avg_spawns": 254, + "spawn_time": "03:41", + "multipliers": [2.27], + "weaknesses": [ + "Electric", + "Grass" + ], + "next_evolution": [{ + "num": "055", + "name": "Golduck" + }] + }, { + "id": 55, + "num": "055", + "name": "Golduck", + "img": "http://www.serebii.net/pokemongo/pokemon/055.png", + "type": [ + "Water" + ], + "height": "1.70 m", + "weight": "76.6 kg", + "candy": "Psyduck Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.087, + "avg_spawns": 8.7, + "spawn_time": "23:06", + "multipliers": [], + "weaknesses": [ + "Electric", + "Grass" + ], + "prev_evolution": [{ + "num": "054", + "name": "Psyduck" + }] + }, { + "id": 56, + "num": "056", + "name": "Mankey", + "img": "http://www.serebii.net/pokemongo/pokemon/056.png", + "type": [ + "Fighting" + ], + "height": "0.51 m", + "weight": "28.0 kg", + "candy": "Mankey Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 0.92, + "avg_spawns": 92, + "spawn_time": "12:52", + "multipliers": [ + 2.17, + 2.28 + ], + "weaknesses": [ + "Flying", + "Psychic", + "Fairy" + ], + "next_evolution": [{ + "num": "057", + "name": "Primeape" + }] + }, { + "id": 57, + "num": "057", + "name": "Primeape", + "img": "http://www.serebii.net/pokemongo/pokemon/057.png", + "type": [ + "Fighting" + ], + "height": "0.99 m", + "weight": "32.0 kg", + "candy": "Mankey Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.031, + "avg_spawns": 3.1, + "spawn_time": "12:33", + "multipliers": [], + "weaknesses": [ + "Flying", + "Psychic", + "Fairy" + ], + "prev_evolution": [{ + "num": "056", + "name": "Mankey" + }] + }, { + "id": 58, + "num": "058", + "name": "Growlithe", + "img": "http://www.serebii.net/pokemongo/pokemon/058.png", + "type": [ + "Fire" + ], + "height": "0.71 m", + "weight": "19.0 kg", + "candy": "Growlithe Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 0.92, + "avg_spawns": 92, + "spawn_time": "03:57", + "multipliers": [ + 2.31, + 2.36 + ], + "weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "next_evolution": [{ + "num": "059", + "name": "Arcanine" + }] + }, { + "id": 59, + "num": "059", + "name": "Arcanine", + "img": "http://www.serebii.net/pokemongo/pokemon/059.png", + "type": [ + "Fire" + ], + "height": "1.91 m", + "weight": "155.0 kg", + "candy": "Growlithe Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.017, + "avg_spawns": 1.7, + "spawn_time": "03:11", + "multipliers": [], + "weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "prev_evolution": [{ + "num": "058", + "name": "Growlithe" + }] + }, { + "id": 60, + "num": "060", + "name": "Poliwag", + "img": "http://www.serebii.net/pokemongo/pokemon/060.png", + "type": [ + "Water" + ], + "height": "0.61 m", + "weight": "12.4 kg", + "candy": "Poliwag Candy", + "candy_count": 25, + "egg": "5 km", + "spawn_chance": 2.19, + "avg_spawns": 219, + "spawn_time": "03:40", + "multipliers": [ + 1.72, + 1.73 + ], + "weaknesses": [ + "Electric", + "Grass" + ], + "next_evolution": [{ + "num": "061", + "name": "Poliwhirl" + }, { + "num": "062", + "name": "Poliwrath" + }] + }, { + "id": 61, + "num": "061", + "name": "Poliwhirl", + "img": "http://www.serebii.net/pokemongo/pokemon/061.png", + "type": [ + "Water" + ], + "height": "0.99 m", + "weight": "20.0 kg", + "candy": "Poliwag Candy", + "candy_count": 100, + "egg": "Not in Eggs", + "spawn_chance": 0.13, + "avg_spawns": 13, + "spawn_time": "09:14", + "multipliers": [1.95], + "weaknesses": [ + "Electric", + "Grass" + ], + "prev_evolution": [{ + "num": "060", + "name": "Poliwag" + }], + "next_evolution": [{ + "num": "062", + "name": "Poliwrath" + }] + }, { + "id": 62, + "num": "062", + "name": "Poliwrath", + "img": "http://www.serebii.net/pokemongo/pokemon/062.png", + "type": [ + "Water", + "Fighting" + ], + "height": "1.30 m", + "weight": "54.0 kg", + "candy": "Poliwag Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.011, + "avg_spawns": 1.1, + "spawn_time": "01:32", + "multipliers": [], + "weaknesses": [ + "Electric", + "Grass", + "Flying", + "Psychic", + "Fairy" + ], + "prev_evolution": [{ + "num": "060", + "name": "Poliwag" + }, { + "num": "061", + "name": "Poliwhirl" + }] + }, { + "id": 63, + "num": "063", + "name": "Abra", + "img": "http://www.serebii.net/pokemongo/pokemon/063.png", + "type": [ + "Psychic" + ], + "height": "0.89 m", + "weight": "19.5 kg", + "candy": "Abra Candy", + "candy_count": 25, + "egg": "5 km", + "spawn_chance": 0.42, + "avg_spawns": 42, + "spawn_time": "04:30", + "multipliers": [ + 1.36, + 1.95 + ], + "weaknesses": [ + "Bug", + "Ghost", + "Dark" + ], + "next_evolution": [{ + "num": "064", + "name": "Kadabra" + }, { + "num": "065", + "name": "Alakazam" + }] + }, { + "id": 64, + "num": "064", + "name": "Kadabra", + "img": "http://www.serebii.net/pokemongo/pokemon/064.png", + "type": [ + "Psychic" + ], + "height": "1.30 m", + "weight": "56.5 kg", + "candy": "Abra Candy", + "candy_count": 100, + "egg": "Not in Eggs", + "spawn_chance": 0.027, + "avg_spawns": 2.7, + "spawn_time": "11:25", + "multipliers": [1.4], + "weaknesses": [ + "Bug", + "Ghost", + "Dark" + ], + "prev_evolution": [{ + "num": "063", + "name": "Abra" + }], + "next_evolution": [{ + "num": "065", + "name": "Alakazam" + }] + }, { + "id": 65, + "num": "065", + "name": "Alakazam", + "img": "http://www.serebii.net/pokemongo/pokemon/065.png", + "type": [ + "Psychic" + ], + "height": "1.50 m", + "weight": "48.0 kg", + "candy": "Abra Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.0073, + "avg_spawns": 0.73, + "spawn_time": "12:33", + "multipliers": [], + "weaknesses": [ + "Bug", + "Ghost", + "Dark" + ], + "prev_evolution": [{ + "num": "063", + "name": "Abra" + }, { + "num": "064", + "name": "Kadabra" + }] + }, { + "id": 66, + "num": "066", + "name": "Machop", + "img": "http://www.serebii.net/pokemongo/pokemon/066.png", + "type": [ + "Fighting" + ], + "height": "0.79 m", + "weight": "19.5 kg", + "candy": "Machop Candy", + "candy_count": 25, + "egg": "5 km", + "spawn_chance": 0.49, + "avg_spawns": 49, + "spawn_time": "01:55", + "multipliers": [ + 1.64, + 1.65 + ], + "weaknesses": [ + "Flying", + "Psychic", + "Fairy" + ], + "next_evolution": [{ + "num": "067", + "name": "Machoke" + }, { + "num": "068", + "name": "Machamp" + }] + }, { + "id": 67, + "num": "067", + "name": "Machoke", + "img": "http://www.serebii.net/pokemongo/pokemon/067.png", + "type": [ + "Fighting" + ], + "height": "1.50 m", + "weight": "70.5 kg", + "candy": "Machop Candy", + "candy_count": 100, + "egg": "Not in Eggs", + "spawn_chance": 0.034, + "avg_spawns": 3.4, + "spawn_time": "10:32", + "multipliers": [1.7], + "weaknesses": [ + "Flying", + "Psychic", + "Fairy" + ], + "prev_evolution": [{ + "num": "066", + "name": "Machop" + }], + "next_evolution": [{ + "num": "068", + "name": "Machamp" + }] + }, { + "id": 68, + "num": "068", + "name": "Machamp", + "img": "http://www.serebii.net/pokemongo/pokemon/068.png", + "type": [ + "Fighting" + ], + "height": "1.60 m", + "weight": "130.0 kg", + "candy": "Machop Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.0068, + "avg_spawns": 0.68, + "spawn_time": "02:55", + "multipliers": [], + "weaknesses": [ + "Flying", + "Psychic", + "Fairy" + ], + "prev_evolution": [{ + "num": "066", + "name": "Machop" + }, { + "num": "067", + "name": "Machoke" + }] + }, { + "id": 69, + "num": "069", + "name": "Bellsprout", + "img": "http://www.serebii.net/pokemongo/pokemon/069.png", + "type": [ + "Grass", + "Poison" + ], + "height": "0.71 m", + "weight": "4.0 kg", + "candy": "Bellsprout Candy", + "candy_count": 25, + "egg": "5 km", + "spawn_chance": 1.15, + "avg_spawns": 115, + "spawn_time": "04:10", + "multipliers": [1.57], + "weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "next_evolution": [{ + "num": "070", + "name": "Weepinbell" + }, { + "num": "071", + "name": "Victreebel" + }] + }, { + "id": 70, + "num": "070", + "name": "Weepinbell", + "img": "http://www.serebii.net/pokemongo/pokemon/070.png", + "type": [ + "Grass", + "Poison" + ], + "height": "0.99 m", + "weight": "6.4 kg", + "candy": "Bellsprout Candy", + "candy_count": 100, + "egg": "Not in Eggs", + "spawn_chance": 0.072, + "avg_spawns": 7.2, + "spawn_time": "09:45", + "multipliers": [1.59], + "weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "prev_evolution": [{ + "num": "069", + "name": "Bellsprout" + }], + "next_evolution": [{ + "num": "071", + "name": "Victreebel" + }] + }, { + "id": 71, + "num": "071", + "name": "Victreebel", + "img": "http://www.serebii.net/pokemongo/pokemon/071.png", + "type": [ + "Grass", + "Poison" + ], + "height": "1.70 m", + "weight": "15.5 kg", + "candy": "Bellsprout Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.0059, + "avg_spawns": 0.59, + "spawn_time": "12:19", + "multipliers": [], + "weaknesses": [ + "Fire", + "Ice", + "Flying", + "Psychic" + ], + "prev_evolution": [{ + "num": "069", + "name": "Bellsprout" + }, { + "num": "070", + "name": "Weepinbell" + }] + }, { + "id": 72, + "num": "072", + "name": "Tentacool", + "img": "http://www.serebii.net/pokemongo/pokemon/072.png", + "type": [ + "Water", + "Poison" + ], + "height": "0.89 m", + "weight": "45.5 kg", + "candy": "Tentacool Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 0.81, + "avg_spawns": 81, + "spawn_time": "03:20", + "multipliers": [2.52], + "weaknesses": [ + "Electric", + "Ground", + "Psychic" + ], + "next_evolution": [{ + "num": "073", + "name": "Tentacruel" + }] + }, { + "id": 73, + "num": "073", + "name": "Tentacruel", + "img": "http://www.serebii.net/pokemongo/pokemon/073.png", + "type": [ + "Water", + "Poison" + ], + "height": "1.60 m", + "weight": "55.0 kg", + "candy": "Tentacool Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.082, + "avg_spawns": 8.2, + "spawn_time": "23:36", + "multipliers": [], + "weaknesses": [ + "Electric", + "Ground", + "Psychic" + ], + "prev_evolution": [{ + "num": "072", + "name": "Tentacool" + }] + }, { + "id": 74, + "num": "074", + "name": "Geodude", + "img": "http://www.serebii.net/pokemongo/pokemon/074.png", + "type": [ + "Rock", + "Ground" + ], + "height": "0.41 m", + "weight": "20.0 kg", + "candy": "Geodude Candy", + "candy_count": 25, + "egg": "2 km", + "spawn_chance": 1.19, + "avg_spawns": 119, + "spawn_time": "12:40", + "multipliers": [ + 1.75, + 1.76 + ], + "weaknesses": [ + "Water", + "Grass", + "Ice", + "Fighting", + "Ground", + "Steel" + ], + "next_evolution": [{ + "num": "075", + "name": "Graveler" + }, { + "num": "076", + "name": "Golem" + }] + }, { + "id": 75, + "num": "075", + "name": "Graveler", + "img": "http://www.serebii.net/pokemongo/pokemon/075.png", + "type": [ + "Rock", + "Ground" + ], + "height": "0.99 m", + "weight": "105.0 kg", + "candy": "Geodude Candy", + "candy_count": 100, + "egg": "Not in Eggs", + "spawn_chance": 0.071, + "avg_spawns": 7.1, + "spawn_time": "04:53", + "multipliers": [ + 1.64, + 1.72 + ], + "weaknesses": [ + "Water", + "Grass", + "Ice", + "Fighting", + "Ground", + "Steel" + ], + "prev_evolution": [{ + "num": "074", + "name": "Geodude" + }], + "next_evolution": [{ + "num": "076", + "name": "Golem" + }] + }, { + "id": 76, + "num": "076", + "name": "Golem", + "img": "http://www.serebii.net/pokemongo/pokemon/076.png", + "type": [ + "Rock", + "Ground" + ], + "height": "1.40 m", + "weight": "300.0 kg", + "candy": "Geodude Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.0047, + "avg_spawns": 0.47, + "spawn_time": "12:16", + "multipliers": [], + "weaknesses": [ + "Water", + "Grass", + "Ice", + "Fighting", + "Ground", + "Steel" + ], + "prev_evolution": [{ + "num": "074", + "name": "Geodude" + }, { + "num": "075", + "name": "Graveler" + }] + }, { + "id": 77, + "num": "077", + "name": "Ponyta", + "img": "http://www.serebii.net/pokemongo/pokemon/077.png", + "type": [ + "Fire" + ], + "height": "0.99 m", + "weight": "30.0 kg", + "candy": "Ponyta Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 0.51, + "avg_spawns": 51, + "spawn_time": "02:50", + "multipliers": [ + 1.48, + 1.5 + ], + "weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "next_evolution": [{ + "num": "078", + "name": "Rapidash" + }] + }, { + "id": 78, + "num": "078", + "name": "Rapidash", + "img": "http://www.serebii.net/pokemongo/pokemon/078.png", + "type": [ + "Fire" + ], + "height": "1.70 m", + "weight": "95.0 kg", + "candy": "Ponyta Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.011, + "avg_spawns": 1.1, + "spawn_time": "04:00", + "multipliers": [], + "weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "prev_evolution": [{ + "num": "077", + "name": "Ponyta" + }] + }, { + "id": 79, + "num": "079", + "name": "Slowpoke", + "img": "http://www.serebii.net/pokemongo/pokemon/079.png", + "type": [ + "Water", + "Psychic" + ], + "height": "1.19 m", + "weight": "36.0 kg", + "candy": "Slowpoke Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 1.05, + "avg_spawns": 105, + "spawn_time": "07:12", + "multipliers": [2.21], + "weaknesses": [ + "Electric", + "Grass", + "Bug", + "Ghost", + "Dark" + ], + "next_evolution": [{ + "num": "080", + "name": "Slowbro" + }] + }, { + "id": 80, + "num": "080", + "name": "Slowbro", + "img": "http://www.serebii.net/pokemongo/pokemon/080.png", + "type": [ + "Water", + "Psychic" + ], + "height": "1.60 m", + "weight": "78.5 kg", + "candy": "Slowpoke Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.036, + "avg_spawns": 3.6, + "spawn_time": "02:56", + "multipliers": [], + "weaknesses": [ + "Electric", + "Grass", + "Bug", + "Ghost", + "Dark" + ], + "prev_evolution": [{ + "num": "079", + "name": "Slowpoke" + }] + }, { + "id": 81, + "num": "081", + "name": "Magnemite", + "img": "http://www.serebii.net/pokemongo/pokemon/081.png", + "type": [ + "Electric" + ], + "height": "0.30 m", + "weight": "6.0 kg", + "candy": "Magnemite Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 0.71, + "avg_spawns": 71, + "spawn_time": "04:04", + "multipliers": [ + 2.16, + 2.17 + ], + "weaknesses": [ + "Fire", + "Water", + "Ground" + ], + "next_evolution": [{ + "num": "082", + "name": "Magneton" + }] + }, { + "id": 82, + "num": "082", + "name": "Magneton", + "img": "http://www.serebii.net/pokemongo/pokemon/082.png", + "type": [ + "Electric" + ], + "height": "0.99 m", + "weight": "60.0 kg", + "candy": "Magnemite Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.023, + "avg_spawns": 2.3, + "spawn_time": "15:25", + "multipliers": [], + "weaknesses": [ + "Fire", + "Water", + "Ground" + ], + "prev_evolution": [{ + "num": "081", + "name": "Magnemite" + }] + }, { + "id": 83, + "num": "083", + "name": "Farfetch'd", + "img": "http://www.serebii.net/pokemongo/pokemon/083.png", + "type": [ + "Normal", + "Flying" + ], + "height": "0.79 m", + "weight": "15.0 kg", + "candy": "None", + "egg": "5 km", + "spawn_chance": 0.0212, + "avg_spawns": 2.12, + "spawn_time": "01:09", + "multipliers": [], + "weaknesses": [ + "Electric", + "Rock" + ] + }, { + "id": 84, + "num": "084", + "name": "Doduo", + "img": "http://www.serebii.net/pokemongo/pokemon/084.png", + "type": [ + "Normal", + "Flying" + ], + "height": "1.40 m", + "weight": "39.2 kg", + "candy": "Doduo Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 0.52, + "avg_spawns": 52, + "spawn_time": "05:10", + "multipliers": [ + 2.19, + 2.24 + ], + "weaknesses": [ + "Electric", + "Rock" + ], + "next_evolution": [{ + "num": "085", + "name": "Dodrio" + }] + }, { + "id": 85, + "num": "085", + "name": "Dodrio", + "img": "http://www.serebii.net/pokemongo/pokemon/085.png", + "type": [ + "Normal", + "Flying" + ], + "height": "1.80 m", + "weight": "85.2 kg", + "candy": "Doduo Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.22, + "avg_spawns": 22, + "spawn_time": "02:12", + "multipliers": [], + "weaknesses": [ + "Electric", + "Rock" + ], + "prev_evolution": [{ + "num": "084", + "name": "Doduo" + }] + }, { + "id": 86, + "num": "086", + "name": "Seel", + "img": "http://www.serebii.net/pokemongo/pokemon/086.png", + "type": [ + "Water" + ], + "height": "1.09 m", + "weight": "90.0 kg", + "candy": "Seel Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 0.28, + "avg_spawns": 28, + "spawn_time": "06:46", + "multipliers": [ + 1.04, + 1.96 + ], + "weaknesses": [ + "Electric", + "Grass" + ], + "next_evolution": [{ + "num": "087", + "name": "Dewgong" + }] + }, { + "id": 87, + "num": "087", + "name": "Dewgong", + "img": "http://www.serebii.net/pokemongo/pokemon/087.png", + "type": [ + "Water", + "Ice" + ], + "height": "1.70 m", + "weight": "120.0 kg", + "candy": "Seel Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.013, + "avg_spawns": 1.3, + "spawn_time": "06:04", + "multipliers": [], + "weaknesses": [ + "Electric", + "Grass", + "Fighting", + "Rock" + ], + "prev_evolution": [{ + "num": "086", + "name": "Seel" + }] + }, { + "id": 88, + "num": "088", + "name": "Grimer", + "img": "http://www.serebii.net/pokemongo/pokemon/088.png", + "type": [ + "Poison" + ], + "height": "0.89 m", + "weight": "30.0 kg", + "candy": "Grimer Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 0.052, + "avg_spawns": 5.2, + "spawn_time": "15:11", + "multipliers": [2.44], + "weaknesses": [ + "Ground", + "Psychic" + ], + "next_evolution": [{ + "num": "089", + "name": "Muk" + }] + }, { + "id": 89, + "num": "089", + "name": "Muk", + "img": "http://www.serebii.net/pokemongo/pokemon/089.png", + "type": [ + "Poison" + ], + "height": "1.19 m", + "weight": "30.0 kg", + "candy": "Grimer Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.0031, + "avg_spawns": 0.31, + "spawn_time": "01:28", + "multipliers": [], + "weaknesses": [ + "Ground", + "Psychic" + ], + "prev_evolution": [{ + "num": "088", + "name": "Grimer" + }] + }, { + "id": 90, + "num": "090", + "name": "Shellder", + "img": "http://www.serebii.net/pokemongo/pokemon/090.png", + "type": [ + "Water" + ], + "height": "0.30 m", + "weight": "4.0 kg", + "candy": "Shellder Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 0.52, + "avg_spawns": 52, + "spawn_time": "07:39", + "multipliers": [2.65], + "weaknesses": [ + "Electric", + "Grass" + ], + "next_evolution": [{ + "num": "091", + "name": "Cloyster" + }] + }, { + "id": 91, + "num": "091", + "name": "Cloyster", + "img": "http://www.serebii.net/pokemongo/pokemon/091.png", + "type": [ + "Water", + "Ice" + ], + "height": "1.50 m", + "weight": "132.5 kg", + "candy": "Shellder Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.015, + "avg_spawns": 1.5, + "spawn_time": "02:33", + "multipliers": [], + "weaknesses": [ + "Electric", + "Grass", + "Fighting", + "Rock" + ], + "prev_evolution": [{ + "num": "090", + "name": "Shellder" + }] + }, { + "id": 92, + "num": "092", + "name": "Gastly", + "img": "http://www.serebii.net/pokemongo/pokemon/092.png", + "type": [ + "Ghost", + "Poison" + ], + "height": "1.30 m", + "weight": "0.1 kg", + "candy": "Gastly Candy", + "candy_count": 25, + "egg": "5 km", + "spawn_chance": 0.79, + "avg_spawns": 79, + "spawn_time": "04:21", + "multipliers": [1.78], + "weaknesses": [ + "Ground", + "Psychic", + "Ghost", + "Dark" + ], + "next_evolution": [{ + "num": "093", + "name": "Haunter" + }, { + "num": "094", + "name": "Gengar" + }] + }, { + "id": 93, + "num": "093", + "name": "Haunter", + "img": "http://www.serebii.net/pokemongo/pokemon/093.png", + "type": [ + "Ghost", + "Poison" + ], + "height": "1.60 m", + "weight": "0.1 kg", + "candy": "Gastly Candy", + "candy_count": 100, + "egg": "Not in Eggs", + "spawn_chance": 0.052, + "avg_spawns": 5.2, + "spawn_time": "00:10", + "multipliers": [ + 1.56, + 1.8 + ], + "weaknesses": [ + "Ground", + "Psychic", + "Ghost", + "Dark" + ], + "prev_evolution": [{ + "num": "092", + "name": "Gastly" + }], + "next_evolution": [{ + "num": "094", + "name": "Gengar" + }] + }, { + "id": 94, + "num": "094", + "name": "Gengar", + "img": "http://www.serebii.net/pokemongo/pokemon/094.png", + "type": [ + "Ghost", + "Poison" + ], + "height": "1.50 m", + "weight": "40.5 kg", + "candy": "Gastly Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.0067, + "avg_spawns": 0.67, + "spawn_time": "03:55", + "multipliers": [], + "weaknesses": [ + "Ground", + "Psychic", + "Ghost", + "Dark" + ], + "prev_evolution": [{ + "num": "092", + "name": "Gastly" + }, { + "num": "093", + "name": "Haunter" + }] + }, { + "id": 95, + "num": "095", + "name": "Onix", + "img": "http://www.serebii.net/pokemongo/pokemon/095.png", + "type": [ + "Rock", + "Ground" + ], + "height": "8.79 m", + "weight": "210.0 kg", + "candy": "None", + "egg": "10 km", + "spawn_chance": 0.10, + "avg_spawns": 10, + "spawn_time": "01:18", + "multipliers": [], + "weaknesses": [ + "Water", + "Grass", + "Ice", + "Fighting", + "Ground", + "Steel" + ] + }, { + "id": 96, + "num": "096", + "name": "Drowzee", + "img": "http://www.serebii.net/pokemongo/pokemon/096.png", + "type": [ + "Psychic" + ], + "height": "0.99 m", + "weight": "32.4 kg", + "candy": "Drowzee Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 3.21, + "avg_spawns": 321, + "spawn_time": "01:51", + "multipliers": [ + 2.08, + 2.09 + ], + "weaknesses": [ + "Bug", + "Ghost", + "Dark" + ], + "next_evolution": [{ + "num": "097", + "name": "Hypno" + }] + }, { + "id": 97, + "num": "097", + "name": "Hypno", + "img": "http://www.serebii.net/pokemongo/pokemon/097.png", + "type": [ + "Psychic" + ], + "height": "1.60 m", + "weight": "75.6 kg", + "candy": "Drowzee Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.10, + "avg_spawns": 10, + "spawn_time": "02:17", + "multipliers": [], + "weaknesses": [ + "Bug", + "Ghost", + "Dark" + ], + "prev_evolution": [{ + "num": "096", + "name": "Drowzee" + }] + }, { + "id": 98, + "num": "098", + "name": "Krabby", + "img": "http://www.serebii.net/pokemongo/pokemon/098.png", + "type": [ + "Water" + ], + "height": "0.41 m", + "weight": "6.5 kg", + "candy": "Krabby Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 2.12, + "avg_spawns": 212, + "spawn_time": "03:33", + "multipliers": [ + 2.36, + 2.4 + ], + "weaknesses": [ + "Electric", + "Grass" + ], + "next_evolution": [{ + "num": "099", + "name": "Kingler" + }] + }, { + "id": 99, + "num": "099", + "name": "Kingler", + "img": "http://www.serebii.net/pokemongo/pokemon/099.png", + "type": [ + "Water" + ], + "height": "1.30 m", + "weight": "60.0 kg", + "candy": "Krabby Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.062, + "avg_spawns": 6.2, + "spawn_time": "03:44", + "multipliers": [], + "weaknesses": [ + "Electric", + "Grass" + ], + "prev_evolution": [{ + "num": "098", + "name": "Krabby" + }] + }, { + "id": 100, + "num": "100", + "name": "Voltorb", + "img": "http://www.serebii.net/pokemongo/pokemon/100.png", + "type": [ + "Electric" + ], + "height": "0.51 m", + "weight": "10.4 kg", + "candy": "Voltorb Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 0.65, + "avg_spawns": 65, + "spawn_time": "04:36", + "multipliers": [ + 2.01, + 2.02 + ], + "weaknesses": [ + "Ground" + ], + "next_evolution": [{ + "num": "101", + "name": "Electrode" + }] + }, { + "id": 101, + "num": "101", + "name": "Electrode", + "img": "http://www.serebii.net/pokemongo/pokemon/101.png", + "type": [ + "Electric" + ], + "height": "1.19 m", + "weight": "66.6 kg", + "candy": "Voltorb Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.02, + "avg_spawns": 2, + "spawn_time": "04:10", + "multipliers": [], + "weaknesses": [ + "Ground" + ], + "prev_evolution": [{ + "num": "100", + "name": "Voltorb" + }] + }, { + "id": 102, + "num": "102", + "name": "Exeggcute", + "img": "http://www.serebii.net/pokemongo/pokemon/102.png", + "type": [ + "Grass", + "Psychic" + ], + "height": "0.41 m", + "weight": "2.5 kg", + "candy": "Exeggcute Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 0.78, + "avg_spawns": 78, + "spawn_time": "09:09", + "multipliers": [ + 2.7, + 3.18 + ], + "weaknesses": [ + "Fire", + "Ice", + "Poison", + "Flying", + "Bug", + "Ghost", + "Dark" + ], + "next_evolution": [{ + "num": "103", + "name": "Exeggutor" + }] + }, { + "id": 103, + "num": "103", + "name": "Exeggutor", + "img": "http://www.serebii.net/pokemongo/pokemon/103.png", + "type": [ + "Grass", + "Psychic" + ], + "height": "2.01 m", + "weight": "120.0 kg", + "candy": "Exeggcute Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.014, + "avg_spawns": 1.4, + "spawn_time": "12:34", + "multipliers": [], + "weaknesses": [ + "Fire", + "Ice", + "Poison", + "Flying", + "Bug", + "Ghost", + "Dark" + ], + "prev_evolution": [{ + "num": "102", + "name": "Exeggcute" + }] + }, { + "id": 104, + "num": "104", + "name": "Cubone", + "img": "http://www.serebii.net/pokemongo/pokemon/104.png", + "type": [ + "Ground" + ], + "height": "0.41 m", + "weight": "6.5 kg", + "candy": "Cubone Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 0.61, + "avg_spawns": 61, + "spawn_time": "01:51", + "multipliers": [1.67], + "weaknesses": [ + "Water", + "Grass", + "Ice" + ], + "next_evolution": [{ + "num": "105", + "name": "Marowak" + }] + }, { + "id": 105, + "num": "105", + "name": "Marowak", + "img": "http://www.serebii.net/pokemongo/pokemon/105.png", + "type": [ + "Ground" + ], + "height": "0.99 m", + "weight": "45.0 kg", + "candy": "Cubone Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.02, + "avg_spawns": 2, + "spawn_time": "03:59", + "multipliers": [], + "weaknesses": [ + "Water", + "Grass", + "Ice" + ], + "prev_evolution": [{ + "num": "104", + "name": "Cubone" + }] + }, { + "id": 106, + "num": "106", + "name": "Hitmonlee", + "img": "http://www.serebii.net/pokemongo/pokemon/106.png", + "type": [ + "Fighting" + ], + "height": "1.50 m", + "weight": "49.8 kg", + "candy": "None", + "egg": "10 km", + "spawn_chance": 0.02, + "avg_spawns": 2, + "spawn_time": "03:59", + "multipliers": [], + "weaknesses": [ + "Flying", + "Psychic", + "Fairy" + ] + }, { + "id": 107, + "num": "107", + "name": "Hitmonchan", + "img": "http://www.serebii.net/pokemongo/pokemon/107.png", + "type": [ + "Fighting" + ], + "height": "1.40 m", + "weight": "50.2 kg", + "candy": "None", + "egg": "10 km", + "spawn_chance": 0.022, + "avg_spawns": 2.2, + "spawn_time": "05:58", + "multipliers": [], + "weaknesses": [ + "Flying", + "Psychic", + "Fairy" + ] + }, { + "id": 108, + "num": "108", + "name": "Lickitung", + "img": "http://www.serebii.net/pokemongo/pokemon/108.png", + "type": [ + "Normal" + ], + "height": "1.19 m", + "weight": "65.5 kg", + "candy": "None", + "egg": "5 km", + "spawn_chance": 0.011, + "avg_spawns": 1.1, + "spawn_time": "02:46", + "multipliers": [], + "weaknesses": [ + "Fighting" + ] + }, { + "id": 109, + "num": "109", + "name": "Koffing", + "img": "http://www.serebii.net/pokemongo/pokemon/109.png", + "type": [ + "Poison" + ], + "height": "0.61 m", + "weight": "1.0 kg", + "candy": "Koffing Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 0.20, + "avg_spawns": 20, + "spawn_time": "08:16", + "multipliers": [1.11], + "weaknesses": [ + "Ground", + "Psychic" + ], + "next_evolution": [{ + "num": "110", + "name": "Weezing" + }] + }, { + "id": 110, + "num": "110", + "name": "Weezing", + "img": "http://www.serebii.net/pokemongo/pokemon/110.png", + "type": [ + "Poison" + ], + "height": "1.19 m", + "weight": "9.5 kg", + "candy": "Koffing Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.016, + "avg_spawns": 1.6, + "spawn_time": "12:17", + "multipliers": [], + "weaknesses": [ + "Ground", + "Psychic" + ], + "prev_evolution": [{ + "num": "109", + "name": "Koffing" + }] + }, { + "id": 111, + "num": "111", + "name": "Rhyhorn", + "img": "http://www.serebii.net/pokemongo/pokemon/111.png", + "type": [ + "Ground", + "Rock" + ], + "height": "0.99 m", + "weight": "115.0 kg", + "candy": "Rhyhorn Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 0.63, + "avg_spawns": 63, + "spawn_time": "03:21", + "multipliers": [1.91], + "weaknesses": [ + "Water", + "Grass", + "Ice", + "Fighting", + "Ground", + "Steel" + ], + "next_evolution": [{ + "num": "112", + "name": "Rhydon" + }] + }, { + "id": 112, + "num": "112", + "name": "Rhydon", + "img": "http://www.serebii.net/pokemongo/pokemon/112.png", + "type": [ + "Ground", + "Rock" + ], + "height": "1.91 m", + "weight": "120.0 kg", + "candy": "Rhyhorn Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.022, + "avg_spawns": 2.2, + "spawn_time": "05:50", + "multipliers": [], + "weaknesses": [ + "Water", + "Grass", + "Ice", + "Fighting", + "Ground", + "Steel" + ], + "prev_evolution": [{ + "num": "111", + "name": "Rhyhorn" + }] + }, { + "id": 113, + "num": "113", + "name": "Chansey", + "img": "http://www.serebii.net/pokemongo/pokemon/113.png", + "type": [ + "Normal" + ], + "height": "1.09 m", + "weight": "34.6 kg", + "candy": "None", + "egg": "10 km", + "spawn_chance": 0.013, + "avg_spawns": 1.3, + "spawn_time": "04:46", + "multipliers": [], + "weaknesses": [ + "Fighting" + ] + }, { + "id": 114, + "num": "114", + "name": "Tangela", + "img": "http://www.serebii.net/pokemongo/pokemon/114.png", + "type": [ + "Grass" + ], + "height": "0.99 m", + "weight": "35.0 kg", + "candy": "None", + "egg": "5 km", + "spawn_chance": 0.228, + "avg_spawns": 22.8, + "spawn_time": "23:13", + "multipliers": [], + "weaknesses": [ + "Fire", + "Ice", + "Poison", + "Flying", + "Bug" + ] + }, { + "id": 115, + "num": "115", + "name": "Kangaskhan", + "img": "http://www.serebii.net/pokemongo/pokemon/115.png", + "type": [ + "Normal" + ], + "height": "2.21 m", + "weight": "80.0 kg", + "candy": "None", + "egg": "5 km", + "spawn_chance": 0.0086, + "avg_spawns": 0.86, + "spawn_time": "02:40", + "multipliers": [], + "weaknesses": [ + "Fighting" + ] + }, { + "id": 116, + "num": "116", + "name": "Horsea", + "img": "http://www.serebii.net/pokemongo/pokemon/116.png", + "type": [ + "Water" + ], + "height": "0.41 m", + "weight": "8.0 kg", + "candy": "Horsea Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 1.13, + "avg_spawns": 113, + "spawn_time": "02:53", + "multipliers": [2.23], + "weaknesses": [ + "Electric", + "Grass" + ], + "next_evolution": [{ + "num": "117", + "name": "Seadra" + }] + }, { + "id": 117, + "num": "117", + "name": "Seadra", + "img": "http://www.serebii.net/pokemongo/pokemon/117.png", + "type": [ + "Water" + ], + "height": "1.19 m", + "weight": "25.0 kg", + "candy": "Horsea Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.034, + "avg_spawns": 3.4, + "spawn_time": "03:18", + "multipliers": [], + "weaknesses": [ + "Electric", + "Grass" + ], + "prev_evolution": [{ + "num": "116", + "name": "Horsea" + }] + }, { + "id": 118, + "num": "118", + "name": "Goldeen", + "img": "http://www.serebii.net/pokemongo/pokemon/118.png", + "type": [ + "Water" + ], + "height": "0.61 m", + "weight": "15.0 kg", + "candy": "Goldeen Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 2.18, + "avg_spawns": 218, + "spawn_time": "03:14", + "multipliers": [ + 2.15, + 2.2 + ], + "weaknesses": [ + "Electric", + "Grass" + ], + "next_evolution": [{ + "num": "119", + "name": "Seaking" + }] + }, { + "id": 119, + "num": "119", + "name": "Seaking", + "img": "http://www.serebii.net/pokemongo/pokemon/119.png", + "type": [ + "Water" + ], + "height": "1.30 m", + "weight": "39.0 kg", + "candy": "Goldeen Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.08, + "avg_spawns": 8, + "spawn_time": "05:21", + "multipliers": [], + "weaknesses": [ + "Electric", + "Grass" + ], + "prev_evolution": [{ + "num": "118", + "name": "Goldeen" + }] + }, { + "id": 120, + "num": "120", + "name": "Staryu", + "img": "http://www.serebii.net/pokemongo/pokemon/120.png", + "type": [ + "Water" + ], + "height": "0.79 m", + "weight": "34.5 kg", + "candy": "Staryu Candy", + "candy_count": 50, + "egg": "5 km", + "spawn_chance": 1.95, + "avg_spawns": 195, + "spawn_time": "22:59", + "multipliers": [ + 2.38, + 2.41 + ], + "weaknesses": [ + "Electric", + "Grass" + ], + "next_evolution": [{ + "num": "121", + "name": "Starmie" + }] + }, { + "id": 121, + "num": "121", + "name": "Starmie", + "img": "http://www.serebii.net/pokemongo/pokemon/121.png", + "type": [ + "Water", + "Psychic" + ], + "height": "1.09 m", + "weight": "80.0 kg", + "candy": "Staryu Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.034, + "avg_spawns": 3.4, + "spawn_time": "06:57", + "multipliers": [], + "weaknesses": [ + "Electric", + "Grass", + "Bug", + "Ghost", + "Dark" + ], + "prev_evolution": [{ + "num": "120", + "name": "Staryu" + }] + }, { + "id": 122, + "num": "122", + "name": "Mr. Mime", + "img": "http://www.serebii.net/pokemongo/pokemon/122.png", + "type": [ + "Psychic" + ], + "height": "1.30 m", + "weight": "54.5 kg", + "candy": "None", + "egg": "10 km", + "spawn_chance": 0.0031, + "avg_spawns": 0.31, + "spawn_time": "01:51", + "multipliers": [], + "weaknesses": [ + "Bug", + "Ghost", + "Dark" + ] + }, { + "id": 123, + "num": "123", + "name": "Scyther", + "img": "http://www.serebii.net/pokemongo/pokemon/123.png", + "type": [ + "Bug", + "Flying" + ], + "height": "1.50 m", + "weight": "56.0 kg", + "candy": "None", + "egg": "10 km", + "spawn_chance": 0.14, + "avg_spawns": 14, + "spawn_time": "05:43", + "multipliers": [], + "weaknesses": [ + "Fire", + "Electric", + "Ice", + "Flying", + "Rock" + ] + }, { + "id": 124, + "num": "124", + "name": "Jynx", + "img": "http://www.serebii.net/pokemongo/pokemon/124.png", + "type": [ + "Ice", + "Psychic" + ], + "height": "1.40 m", + "weight": "40.6 kg", + "candy": "None", + "egg": "10 km", + "spawn_chance": 0.35, + "avg_spawns": 35, + "spawn_time": "05:41", + "multipliers": [], + "weaknesses": [ + "Fire", + "Bug", + "Rock", + "Ghost", + "Dark", + "Steel" + ] + }, { + "id": 125, + "num": "125", + "name": "Electabuzz", + "img": "http://www.serebii.net/pokemongo/pokemon/125.png", + "type": [ + "Electric" + ], + "height": "1.09 m", + "weight": "30.0 kg", + "candy": "None", + "egg": "10 km", + "spawn_chance": 0.074, + "avg_spawns": 7.4, + "spawn_time": "04:28", + "multipliers": [], + "weaknesses": [ + "Ground" + ] + }, { + "id": 126, + "num": "126", + "name": "Magmar", + "img": "http://www.serebii.net/pokemongo/pokemon/126.png", + "type": [ + "Fire" + ], + "height": "1.30 m", + "weight": "44.5 kg", + "candy": "None", + "egg": "10 km", + "spawn_chance": 0.10, + "avg_spawns": 10, + "spawn_time": "20:36", + "multipliers": [], + "weaknesses": [ + "Water", + "Ground", + "Rock" + ] + }, { + "id": 127, + "num": "127", + "name": "Pinsir", + "img": "http://www.serebii.net/pokemongo/pokemon/127.png", + "type": [ + "Bug" + ], + "height": "1.50 m", + "weight": "55.0 kg", + "candy": "None", + "egg": "10 km", + "spawn_chance": 0.99, + "avg_spawns": 99, + "spawn_time": "03:25", + "multipliers": [], + "weaknesses": [ + "Fire", + "Flying", + "Rock" + ] + }, { + "id": 128, + "num": "128", + "name": "Tauros", + "img": "http://www.serebii.net/pokemongo/pokemon/128.png", + "type": [ + "Normal" + ], + "height": "1.40 m", + "weight": "88.4 kg", + "candy": "None", + "egg": "5 km", + "spawn_chance": 0.12, + "avg_spawns": 12, + "spawn_time": "00:37", + "multipliers": [], + "weaknesses": [ + "Fighting" + ] + }, { + "id": 129, + "num": "129", + "name": "Magikarp", + "img": "http://www.serebii.net/pokemongo/pokemon/129.png", + "type": [ + "Water" + ], + "height": "0.89 m", + "weight": "10.0 kg", + "candy": "Magikarp Candy", + "candy_count": 400, + "egg": "2 km", + "spawn_chance": 4.78, + "avg_spawns": 478, + "spawn_time": "14:26", + "multipliers": [ + 10.1, + 11.8 + ], + "weaknesses": [ + "Electric", + "Grass" + ], + "next_evolution": [{ + "num": "130", + "name": "Gyarados" + }] + }, { + "id": 130, + "num": "130", + "name": "Gyarados", + "img": "http://www.serebii.net/pokemongo/pokemon/130.png", + "type": [ + "Water", + "Flying" + ], + "height": "6.50 m", + "weight": "235.0 kg", + "candy": "Magikarp Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.0032, + "avg_spawns": 0.32, + "spawn_time": "02:15", + "multipliers": [], + "weaknesses": [ + "Electric", + "Rock" + ], + "prev_evolution": [{ + "num": "129", + "name": "Magikarp" + }] + }, { + "id": 131, + "num": "131", + "name": "Lapras", + "img": "http://www.serebii.net/pokemongo/pokemon/131.png", + "type": [ + "Water", + "Ice" + ], + "height": "2.49 m", + "weight": "220.0 kg", + "candy": "None", + "egg": "10 km", + "spawn_chance": 0.006, + "avg_spawns": 0.6, + "spawn_time": "08:59", + "multipliers": [], + "weaknesses": [ + "Electric", + "Grass", + "Fighting", + "Rock" + ] + }, { + "id": 132, + "num": "132", + "name": "Ditto", + "img": "http://www.serebii.net/pokemongo/pokemon/132.png", + "type": [ + "Normal" + ], + "height": "0.30 m", + "weight": "4.0 kg", + "candy": "None", + "egg": "Not in Eggs", + "spawn_chance": 0, + "avg_spawns": 0, + "spawn_time": "N/A", + "multipliers": [], + "weaknesses": [ + "Fighting" + ] + }, { + "id": 133, + "num": "133", + "name": "Eevee", + "img": "http://www.serebii.net/pokemongo/pokemon/133.png", + "type": [ + "Normal" + ], + "height": "0.30 m", + "weight": "6.5 kg", + "candy": "Eevee Candy", + "candy_count": 25, + "egg": "10 km", + "spawn_chance": 2.75, + "avg_spawns": 275, + "spawn_time": "05:32", + "multipliers": [ + 2.02, + 2.64 + ], + "weaknesses": [ + "Fighting" + ], + "next_evolution": [{ + "num": "134", + "name": "Vaporeon" + }, { + "num": "135", + "name": "Jolteon" + }, { + "num": "136", + "name": "Flareon" + }] + }, { + "id": 134, + "num": "134", + "name": "Vaporeon", + "img": "http://www.serebii.net/pokemongo/pokemon/134.png", + "type": [ + "Water" + ], + "height": "0.99 m", + "weight": "29.0 kg", + "candy": "Eevee Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.014, + "avg_spawns": 1.4, + "spawn_time": "10:54", + "multipliers": [], + "weaknesses": [ + "Electric", + "Grass" + ], + "prev_evolution": [{ + "num": "133", + "name": "Eevee" + }] + }, { + "id": 135, + "num": "135", + "name": "Jolteon", + "img": "http://www.serebii.net/pokemongo/pokemon/135.png", + "type": [ + "Electric" + ], + "height": "0.79 m", + "weight": "24.5 kg", + "candy": "None", + "egg": "Not in Eggs", + "spawn_chance": 0.012, + "avg_spawns": 1.2, + "spawn_time": "02:30", + "multipliers": [], + "weaknesses": [ + "Ground" + ], + "prev_evolution": [{ + "num": "133", + "name": "Eevee" + }] + }, { + "id": 136, + "num": "136", + "name": "Flareon", + "img": "http://www.serebii.net/pokemongo/pokemon/136.png", + "type": [ + "Fire" + ], + "height": "0.89 m", + "weight": "25.0 kg", + "candy": "Eevee Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.017, + "avg_spawns": 1.7, + "spawn_time": "07:02", + "multipliers": [], + "weaknesses": [ + "Water", + "Ground", + "Rock" + ], + "prev_evolution": [{ + "num": "133", + "name": "Eevee" + }] + }, { + "id": 137, + "num": "137", + "name": "Porygon", + "img": "http://www.serebii.net/pokemongo/pokemon/137.png", + "type": [ + "Normal" + ], + "height": "0.79 m", + "weight": "36.5 kg", + "candy": "None", + "egg": "5 km", + "spawn_chance": 0.012, + "avg_spawns": 1.2, + "spawn_time": "02:49", + "multipliers": [], + "weaknesses": [ + "Fighting" + ] + }, { + "id": 138, + "num": "138", + "name": "Omanyte", + "img": "http://www.serebii.net/pokemongo/pokemon/138.png", + "type": [ + "Rock", + "Water" + ], + "height": "0.41 m", + "weight": "7.5 kg", + "candy": "Omanyte Candy", + "candy_count": 50, + "egg": "10 km", + "spawn_chance": 0.14, + "avg_spawns": 14, + "spawn_time": "10:23", + "multipliers": [2.12], + "weaknesses": [ + "Electric", + "Grass", + "Fighting", + "Ground" + ], + "next_evolution": [{ + "num": "139", + "name": "Omastar" + }] + }, { + "id": 139, + "num": "139", + "name": "Omastar", + "img": "http://www.serebii.net/pokemongo/pokemon/139.png", + "type": [ + "Rock", + "Water" + ], + "height": "0.99 m", + "weight": "35.0 kg", + "candy": "None", + "egg": "Omanyte Candy", + "spawn_chance": 0.0061, + "avg_spawns": 0.61, + "spawn_time": "05:04", + "multipliers": [], + "weaknesses": [ + "Electric", + "Grass", + "Fighting", + "Ground" + ], + "prev_evolution": [{ + "num": "138", + "name": "Omanyte" + }] + }, { + "id": 140, + "num": "140", + "name": "Kabuto", + "img": "http://www.serebii.net/pokemongo/pokemon/140.png", + "type": [ + "Rock", + "Water" + ], + "height": "0.51 m", + "weight": "11.5 kg", + "candy": "Kabuto Candy", + "candy_count": 50, + "egg": "10 km", + "spawn_chance": 0.10, + "avg_spawns": 10, + "spawn_time": "00:05", + "multipliers": [ + 1.97, + 2.37 + ], + "weaknesses": [ + "Electric", + "Grass", + "Fighting", + "Ground" + ], + "next_evolution": [{ + "num": "141", + "name": "Kabutops" + }] + }, { + "id": 141, + "num": "141", + "name": "Kabutops", + "img": "http://www.serebii.net/pokemongo/pokemon/141.png", + "type": [ + "Rock", + "Water" + ], + "height": "1.30 m", + "weight": "40.5 kg", + "candy": "Kabuto Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.0032, + "avg_spawns": 0.32, + "spawn_time": "23:40", + "multipliers": [], + "weaknesses": [ + "Electric", + "Grass", + "Fighting", + "Ground" + ], + "prev_evolution": [{ + "num": "140", + "name": "Kabuto" + }] + }, { + "id": 142, + "num": "142", + "name": "Aerodactyl", + "img": "http://www.serebii.net/pokemongo/pokemon/142.png", + "type": [ + "Rock", + "Flying" + ], + "height": "1.80 m", + "weight": "59.0 kg", + "candy": "None", + "egg": "10 km", + "spawn_chance": 0.018, + "avg_spawns": 1.8, + "spawn_time": "23:40", + "multipliers": [], + "weaknesses": [ + "Water", + "Electric", + "Ice", + "Rock", + "Steel" + ] + }, { + "id": 143, + "num": "143", + "name": "Snorlax", + "img": "http://www.serebii.net/pokemongo/pokemon/143.png", + "type": [ + "Normal" + ], + "height": "2.11 m", + "weight": "460.0 kg", + "candy": "None", + "egg": "10 km", + "spawn_chance": 0.016, + "avg_spawns": 1.6, + "spawn_time": "23:40", + "multipliers": [], + "weaknesses": [ + "Fighting" + ] + }, { + "id": 144, + "num": "144", + "name": "Articuno", + "img": "http://www.serebii.net/pokemongo/pokemon/144.png", + "type": [ + "Ice", + "Flying" + ], + "height": "1.70 m", + "weight": "55.4 kg", + "candy": "None", + "egg": "Not in Eggs", + "spawn_chance": 0, + "avg_spawns": 0, + "spawn_time": "N/A", + "multipliers": [], + "weaknesses": [ + "Fire", + "Electric", + "Rock", + "Steel" + ] + }, { + "id": 145, + "num": "145", + "name": "Zapdos", + "img": "http://www.serebii.net/pokemongo/pokemon/145.png", + "type": [ + "Electric", + "Flying" + ], + "height": "1.60 m", + "weight": "52.6 kg", + "candy": "None", + "egg": "Not in Eggs", + "spawn_chance": 0, + "avg_spawns": 0, + "spawn_time": "N/A", + "multipliers": [], + "weaknesses": [ + "Ice", + "Rock" + ] + }, { + "id": 146, + "num": "146", + "name": "Moltres", + "img": "http://www.serebii.net/pokemongo/pokemon/146.png", + "type": [ + "Fire", + "Flying" + ], + "height": "2.01 m", + "weight": "60.0 kg", + "candy": "None", + "egg": "Not in Eggs", + "spawn_chance": 0, + "avg_spawns": 0, + "spawn_time": "N/A", + "multipliers": [], + "weaknesses": [ + "Water", + "Electric", + "Rock" + ] + }, { + "id": 147, + "num": "147", + "name": "Dratini", + "img": "http://www.serebii.net/pokemongo/pokemon/147.png", + "type": [ + "Dragon" + ], + "height": "1.80 m", + "weight": "3.3 kg", + "candy": "Dratini Candy", + "candy_count": 25, + "egg": "10 km", + "spawn_chance": 0.30, + "avg_spawns": 30, + "spawn_time": "06:41", + "multipliers": [ + 1.83, + 1.84 + ], + "weaknesses": [ + "Ice", + "Dragon", + "Fairy" + ], + "next_evolution": [{ + "num": "148", + "name": "Dragonair" + }, { + "num": "149", + "name": "Dragonite" + }] + }, { + "id": 148, + "num": "148", + "name": "Dragonair", + "img": "http://www.serebii.net/pokemongo/pokemon/148.png", + "type": [ + "Dragon" + ], + "height": "3.99 m", + "weight": "16.5 kg", + "candy": "Dratini Candy", + "candy_count": 100, + "egg": "Not in Eggs", + "spawn_chance": 0.02, + "avg_spawns": 2, + "spawn_time": "11:57", + "multipliers": [2.05], + "weaknesses": [ + "Ice", + "Dragon", + "Fairy" + ], + "prev_evolution": [{ + "num": "147", + "name": "Dratini" + }], + "next_evolution": [{ + "num": "149", + "name": "Dragonite" + }] + }, { + "id": 149, + "num": "149", + "name": "Dragonite", + "img": "http://www.serebii.net/pokemongo/pokemon/149.png", + "type": [ + "Dragon", + "Flying" + ], + "height": "2.21 m", + "weight": "210.0 kg", + "candy": "Dratini Candy", + "egg": "Not in Eggs", + "spawn_chance": 0.0011, + "avg_spawns": 0.11, + "spawn_time": "23:38", + "multipliers": [], + "weaknesses": [ + "Ice", + "Rock", + "Dragon", + "Fairy" + ], + "prev_evolution": [{ + "num": "147", + "name": "Dratini" + }, { + "num": "148", + "name": "Dragonair" + }] + }, { + "id": 150, + "num": "150", + "name": "Mewtwo", + "img": "http://www.serebii.net/pokemongo/pokemon/150.png", + "type": [ + "Psychic" + ], + "height": "2.01 m", + "weight": "122.0 kg", + "candy": "None", + "egg": "Not in Eggs", + "spawn_chance": 0, + "avg_spawns": 0, + "spawn_time": "N/A", + "multipliers": [], + "weaknesses": [ + "Bug", + "Ghost", + "Dark" + ] + }, { + "id": 151, + "num": "151", + "name": "Mew", + "img": "http://www.serebii.net/pokemongo/pokemon/151.png", + "type": [ + "Psychic" + ], + "height": "0.41 m", + "weight": "4.0 kg", + "candy": "None", + "egg": "Not in Eggs", + "spawn_chance": 0, + "avg_spawns": 0, + "spawn_time": "N/A", + "multipliers": [], + "weaknesses": [ + "Bug", + "Ghost", + "Dark" + ] + }] +} diff --git a/tp-json/tests/json/string_hello.json b/tp-json/tests/json/string_hello.json new file mode 100644 index 00000000..5638d8ba --- /dev/null +++ b/tp-json/tests/json/string_hello.json @@ -0,0 +1 @@ +"Hello" \ No newline at end of file diff --git a/tp-json/tests/json/string_nasty.json b/tp-json/tests/json/string_nasty.json new file mode 100644 index 00000000..36dc22db --- /dev/null +++ b/tp-json/tests/json/string_nasty.json @@ -0,0 +1 @@ +"{ \"truefalse\" : [ \",\\,\" ] }" \ No newline at end of file