diff options
author | mukai@chromium.org <mukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-22 18:31:47 +0000 |
---|---|---|
committer | mukai@chromium.org <mukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-22 18:31:47 +0000 |
commit | 193f946be6f110983d57cb1c6eacb9fffd0bdd91 (patch) | |
tree | d155d69549ca9132077cf27a101df6a8b2d2635f | |
parent | 0bfbf88d75aae7adf722238e174dd11e07c9d133 (diff) | |
download | chromium_src-193f946be6f110983d57cb1c6eacb9fffd0bdd91.zip chromium_src-193f946be6f110983d57cb1c6eacb9fffd0bdd91.tar.gz chromium_src-193f946be6f110983d57cb1c6eacb9fffd0bdd91.tar.bz2 |
Add JSONValueConverter.
JSONValueConverter will convert a Value object (from JSON) into a C++ struct. All you will do is to register the mapping between field name and actual C++ field, then:
JSONValueConverter<Message> converter;
Message msg;
converter.Convert(json, &msg);
will scan json and fill fields in msg. See comments in .h and unittests.cc for examples.
BUG=chromium-os:23508
TEST=passed
Review URL: http://codereview.chromium.org/8774002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@115562 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | base/base.gyp | 1 | ||||
-rw-r--r-- | base/base.gypi | 2 | ||||
-rw-r--r-- | base/json/json_value_converter.cc | 20 | ||||
-rw-r--r-- | base/json/json_value_converter.h | 350 | ||||
-rw-r--r-- | base/json/json_value_converter_unittest.cc | 116 |
5 files changed, 489 insertions, 0 deletions
diff --git a/base/base.gyp b/base/base.gyp index 7295711..23eb7bb0 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -158,6 +158,7 @@ 'i18n/string_search_unittest.cc', 'i18n/time_formatting_unittest.cc', 'json/json_reader_unittest.cc', + 'json/json_value_converter_unittest.cc', 'json/json_value_serializer_unittest.cc', 'json/json_writer_unittest.cc', 'json/string_escape_unittest.cc', diff --git a/base/base.gypi b/base/base.gypi index 472e70ae..7a99941 100644 --- a/base/base.gypi +++ b/base/base.gypi @@ -126,6 +126,8 @@ 'id_map.h', 'json/json_reader.cc', 'json/json_reader.h', + 'json/json_value_converter.cc', + 'json/json_value_converter.h', 'json/json_value_serializer.cc', 'json/json_value_serializer.h', 'json/json_writer.cc', diff --git a/base/json/json_value_converter.cc b/base/json/json_value_converter.cc new file mode 100644 index 0000000..1870a0e --- /dev/null +++ b/base/json/json_value_converter.cc @@ -0,0 +1,20 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/json/json_value_converter.h" + +#include <string> + +namespace base { +namespace internal { + +FieldConverterBase::FieldConverterBase(const std::string& path) + : field_path_(path) { +} + +FieldConverterBase::~FieldConverterBase() { +} + +} // namespace internal +} // namespace base diff --git a/base/json/json_value_converter.h b/base/json/json_value_converter.h new file mode 100644 index 0000000..64cd90b --- /dev/null +++ b/base/json/json_value_converter.h @@ -0,0 +1,350 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_JSON_JSON_VALUE_CONVERTER_H_ +#define BASE_JSON_JSON_VALUE_CONVERTER_H_ +#pragma once + +#include <string> +#include <vector> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "base/values.h" + +// JSONValueConverter converts a JSON value into a C++ struct in a +// lightweight way. +// +// Usage: +// For real examples, you may want to refer to _unittest.cc file. +// +// Assume that you have a struct like this: +// struct Message { +// int foo; +// std::string bar; +// static void RegisterJSONConverter( +// JSONValueConverter<Message>* converter); +// }; +// +// And you want to parse a json data into this struct. First, you +// need to declare RegisterJSONConverter() method in your struct. +// // static +// void Message::RegisterJSONConverter( +// JSONValueConverter<Message>* converter) { +// converter->RegisterIntField("foo", &Message::foo); +// converter->RegisterStringField("bar", &Message::bar); +// } +// +// Then, you just instantiate your JSONValueConverter of your type and call +// Convert() method. +// Message message; +// JSONValueConverter<Message> converter; +// converter.Convert(json, &message); +// +// For nested field, the internal message also has to implement the registration +// method. Then, just use RegisterNestedField() from the containing struct's +// RegisterJSONConverter method. +// struct Nested { +// Message foo; +// void RegisterJSONConverter(...) { +// ... +// converter->RegisterNestedField("foo", &Nested::foo); +// } +// }; +// +// For repeated field, we just assume std::vector for its container +// and you can put RegisterRepeatedInt or some other types. Use +// RegisterRepeatedMessage for nested repeated fields. +// + +namespace base { + +template <typename StructType> +class JSONValueConverter; + +namespace internal { + +class FieldConverterBase { + public: + BASE_EXPORT explicit FieldConverterBase(const std::string& path); + BASE_EXPORT virtual ~FieldConverterBase(); + virtual void ConvertField(const base::Value& value, void* obj) const = 0; + const std::string& field_path() const { return field_path_; } + + private: + std::string field_path_; + DISALLOW_COPY_AND_ASSIGN(FieldConverterBase); +}; + +template <typename FieldType> +class ValueConverter { + public: + virtual ~ValueConverter() {} + virtual void Convert(const base::Value& value, FieldType* field) const = 0; +}; + +template <typename StructType, typename FieldType> +class FieldConverter : public FieldConverterBase { + public: + explicit FieldConverter(const std::string& path, + FieldType StructType::* field, + ValueConverter<FieldType>* converter) + : FieldConverterBase(path), + field_pointer_(field), + value_converter_(converter) { + } + + virtual void ConvertField( + const base::Value& value, void* obj) const OVERRIDE { + StructType* dst = reinterpret_cast<StructType*>(obj); + value_converter_->Convert(value, &(dst->*field_pointer_)); + } + + private: + FieldType StructType::* field_pointer_; + scoped_ptr<ValueConverter<FieldType> > value_converter_; + DISALLOW_COPY_AND_ASSIGN(FieldConverter); +}; + +template <typename FieldType> +class BasicValueConverter; + +template <> +class BasicValueConverter<int> : public ValueConverter<int> { + public: + BasicValueConverter() {} + + virtual void Convert(const base::Value& value, int* field) const OVERRIDE { + value.GetAsInteger(field); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BasicValueConverter); +}; + +template <> +class BasicValueConverter<std::string> : public ValueConverter<std::string> { + public: + BasicValueConverter() {} + + virtual void Convert( + const base::Value& value, std::string* field) const OVERRIDE { + value.GetAsString(field); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BasicValueConverter); +}; + +template <> +class BasicValueConverter<double> : public ValueConverter<double> { + public: + BasicValueConverter() {} + + virtual void Convert(const base::Value& value, double* field) const OVERRIDE { + value.GetAsDouble(field); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BasicValueConverter); +}; + +template <> +class BasicValueConverter<bool> : public ValueConverter<bool> { + public: + BasicValueConverter() {} + + virtual void Convert(const base::Value& value, bool* field) const OVERRIDE { + value.GetAsBoolean(field); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BasicValueConverter); +}; + +template <typename NestedType> +class NestedValueConverter : public ValueConverter<NestedType> { + public: + NestedValueConverter() {} + + virtual void Convert( + const base::Value& value, NestedType* field) const OVERRIDE { + converter_.Convert(value, field); + } + + private: + JSONValueConverter<NestedType> converter_; + DISALLOW_COPY_AND_ASSIGN(NestedValueConverter); +}; + +template <typename Element> +class RepeatedValueConverter : public ValueConverter<std::vector<Element> > { + public: + RepeatedValueConverter() {} + + virtual void Convert( + const base::Value& value, std::vector<Element>* field) const OVERRIDE { + const base::ListValue* list = NULL; + if (!value.GetAsList(&list)) { + // The field is not a list. + return; + } + + field->reserve(list->GetSize()); + for (size_t i = 0; i < list->GetSize(); ++i) { + base::Value* element = NULL; + if (!list->Get(i, &element)) + continue; + + Element e; + basic_converter_.Convert(*element, &e); + field->push_back(e); + } + } + + private: + BasicValueConverter<Element> basic_converter_; + DISALLOW_COPY_AND_ASSIGN(RepeatedValueConverter); +}; + +template <typename NestedType> +class RepeatedMessageConverter + : public ValueConverter<std::vector<NestedType> > { + public: + RepeatedMessageConverter() {} + + virtual void Convert( + const base::Value& value, std::vector<NestedType>* field) const OVERRIDE { + const base::ListValue* list = NULL; + if (!value.GetAsList(&list)) + return; + + field->reserve(list->GetSize()); + for (size_t i = 0; i < list->GetSize(); ++i) { + base::Value* element = NULL; + if (!list->Get(i, &element)) + continue; + + field->push_back(NestedType()); + converter_.Convert(*element, &field->back()); + } + } + + private: + JSONValueConverter<NestedType> converter_; + DISALLOW_COPY_AND_ASSIGN(RepeatedMessageConverter); +}; + +} // namespace internal + +template <class StructType> +class JSONValueConverter { + public: + JSONValueConverter() { + StructType::RegisterJSONConverter(this); + } + + ~JSONValueConverter() { + STLDeleteContainerPointers(fields_.begin(), fields_.end()); + } + + void RegisterIntField(const std::string& field_name, + int StructType::* field) { + fields_.push_back(new internal::FieldConverter<StructType, int>( + field_name, field, new internal::BasicValueConverter<int>)); + } + + void RegisterStringField(const std::string& field_name, + std::string StructType::* field) { + fields_.push_back(new internal::FieldConverter<StructType, std::string>( + field_name, field, new internal::BasicValueConverter<std::string>)); + } + + void RegisterBoolField(const std::string& field_name, + bool StructType::* field) { + fields_.push_back(new internal::FieldConverter<StructType, bool>( + field_name, field, new internal::BasicValueConverter<bool>)); + } + + void RegisterDoubleField(const std::string& field_name, + double StructType::* field) { + fields_.push_back(new internal::FieldConverter<StructType, double>( + field_name, field, new internal::BasicValueConverter<double>)); + } + + template <class NestedType> + void RegisterNestedField( + const std::string& field_name, NestedType StructType::* field) { + fields_.push_back(new internal::FieldConverter<StructType, NestedType>( + field_name, + field, + new internal::NestedValueConverter<NestedType>)); + } + + void RegisterRepeatedInt(const std::string& field_name, + std::vector<int> StructType::* field) { + fields_.push_back( + new internal::FieldConverter<StructType, std::vector<int> >( + field_name, field, new internal::RepeatedValueConverter<int>)); + } + + void RegisterRepeatedString(const std::string& field_name, + std::vector<std::string> StructType::* field) { + fields_.push_back( + new internal::FieldConverter<StructType, std::vector<std::string> >( + field_name, + field, + new internal::RepeatedValueConverter<std::string>)); + } + + void RegisterRepeatedDouble(const std::string& field_name, + std::vector<double> StructType::* field) { + fields_.push_back( + new internal::FieldConverter<StructType, std::vector<double> >( + field_name, field, new internal::RepeatedValueConverter<double>)); + } + + void RegisterRepeatedBool(const std::string& field_name, + std::vector<bool> StructType::* field) { + fields_.push_back( + new internal::FieldConverter<StructType, std::vector<bool> >( + field_name, field, new internal::RepeatedValueConverter<bool>)); + } + + template <class NestedType> + void RegisterRepeatedMessage(const std::string& field_name, + std::vector<NestedType> StructType::* field) { + fields_.push_back( + new internal::FieldConverter<StructType, std::vector<NestedType> >( + field_name, + field, + new internal::RepeatedMessageConverter<NestedType>)); + } + + void Convert(const base::Value& value, StructType* output) const { + const DictionaryValue* dictionary_value = NULL; + if (!value.GetAsDictionary(&dictionary_value)) + return; + + for(std::vector<internal::FieldConverterBase*>::const_iterator it = + fields_.begin(); it != fields_.end(); ++it) { + base::Value* field = NULL; + if (dictionary_value->Get((*it)->field_path(), &field)) { + (*it)->ConvertField(*field, output); + } + } + } + + private: + std::vector<internal::FieldConverterBase*> fields_; + + DISALLOW_COPY_AND_ASSIGN(JSONValueConverter); +}; + +} // namespace base + +#endif // BASE_JSON_JSON_VALUE_CONVERTER_H_ diff --git a/base/json/json_value_converter_unittest.cc b/base/json/json_value_converter_unittest.cc new file mode 100644 index 0000000..d1a8ee72 --- /dev/null +++ b/base/json/json_value_converter_unittest.cc @@ -0,0 +1,116 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/json/json_value_converter.h" + +#include <string> +#include <vector> + +#include "base/values.h" +#include "base/json/json_reader.h" +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace { + +// Very simple messages. +struct SimpleMessage { + int foo; + std::string bar; + bool baz; + std::vector<int> ints; + SimpleMessage() : foo(0), baz(false) {} + + static void RegisterJSONConverter( + base::JSONValueConverter<SimpleMessage>* converter) { + converter->RegisterIntField("foo", &SimpleMessage::foo); + converter->RegisterStringField("bar", &SimpleMessage::bar); + converter->RegisterBoolField("baz", &SimpleMessage::baz); + converter->RegisterRepeatedInt("ints", &SimpleMessage::ints); + } +}; + +// For nested messages. +struct NestedMessage { + double foo; + SimpleMessage child; + std::vector<SimpleMessage> children; + + NestedMessage() : foo(0) {} + + static void RegisterJSONConverter( + base::JSONValueConverter<NestedMessage>* converter) { + converter->RegisterDoubleField("foo", &NestedMessage::foo); + converter->RegisterNestedField("child", &NestedMessage::child); + converter->RegisterRepeatedMessage("children", &NestedMessage::children); + } +}; + +} // namespace + +TEST(JSONValueConverterTest, ParseSimpleMessage) { + const char normal_data[] = + "{\n" + " \"foo\": 1,\n" + " \"bar\": \"bar\",\n" + " \"baz\": true,\n" + " \"ints\": [1, 2]" + "}\n"; + + scoped_ptr<Value> value(base::JSONReader::Read(normal_data, false)); + SimpleMessage message; + base::JSONValueConverter<SimpleMessage> converter; + converter.Convert(*value.get(), &message); + EXPECT_EQ(1, message.foo); + EXPECT_EQ("bar", message.bar); + EXPECT_TRUE(message.baz); + EXPECT_EQ(2, static_cast<int>(message.ints.size())); + EXPECT_EQ(1, message.ints[0]); + EXPECT_EQ(2, message.ints[1]); +} + +TEST(JSONValueConverterTest, ParseNestedMessage) { + const char normal_data[] = + "{\n" + " \"foo\": 1.0,\n" + " \"child\": {\n" + " \"foo\": 1,\n" + " \"bar\": \"bar\",\n" + " \"baz\": true\n" + " },\n" + " \"children\": [{\n" + " \"foo\": 2,\n" + " \"bar\": \"foobar\",\n" + " \"baz\": true\n" + " },\n" + " {\n" + " \"foo\": 3,\n" + " \"bar\": \"barbaz\",\n" + " \"baz\": false\n" + " }]\n" + "}\n"; + + scoped_ptr<Value> value(base::JSONReader::Read(normal_data, false)); + NestedMessage message; + base::JSONValueConverter<NestedMessage> converter; + converter.Convert(*value.get(), &message); + EXPECT_EQ(1.0, message.foo); + EXPECT_EQ(1, message.child.foo); + EXPECT_EQ("bar", message.child.bar); + EXPECT_TRUE(message.child.baz); + + EXPECT_EQ(2, static_cast<int>(message.children.size())); + const SimpleMessage& first_child = message.children[0]; + EXPECT_EQ(2, first_child.foo); + EXPECT_EQ("foobar", first_child.bar); + EXPECT_TRUE(first_child.baz); + + const SimpleMessage& second_child = message.children[1]; + EXPECT_EQ(3, second_child.foo); + EXPECT_EQ("barbaz", second_child.bar); + EXPECT_FALSE(second_child.baz); +} + +} // namespace base |