From 173e10fdf14884376eb6ed4590142394d69f62c6 Mon Sep 17 00:00:00 2001
From: mugisoba <51015092+mugisoba@users.noreply.github.com>
Date: Tue, 11 Feb 2020 10:43:36 +0900
Subject: [PATCH] [C#] support Json Serialization (#5752)
* support json serialization
* fix invalid json format.
* string must be written with double quotes.
* remove commma after the last object member.
* fix indent
* Revert "fix invalid json format."
This reverts commit d6820ed50c8e3d3cda3aa1849b3079f853608619.
* quated string value.
* add cs-gen-json-serializer flag.
* fix preprocessor indent
* ENABLE_JSON_SERIALIZATION -> ENABLE_JSON_SERIALIZATION_TEST
* share TestBuffer method
* remove ENABLE_JSON_SERIALIZATION
* remove duplicated test data
* [windows] add nuget restore and copy test data.
* [docker mono] share msbuild settings with windows. add nuget restore and copy test data.
* add some note for json api.
---
appveyor.yml | 5 +
docs/source/CsharpUsage.md | 21 +++
include/flatbuffers/idl.h | 15 +-
src/flatc.cpp | 9 +-
src/idl_gen_csharp.cpp | 161 +++++++++++++++++-
.../FlatBuffers.Test/FlatBuffers.Test.csproj | 12 +-
.../FlatBuffersExampleTests.cs | 34 +++-
tests/FlatBuffers.Test/NetTest.sh | 40 +++--
tests/FlatBuffers.Test/Resources/.gitkeep | 0
.../Resources/monsterdata_test.mon | Bin 448 -> 0 bytes
tests/FlatBuffers.Test/packages.config | 4 +
tests/MyGame/Example/Ability.cs | 2 +
tests/MyGame/Example/Any.cs | 44 +++++
tests/MyGame/Example/AnyAmbiguousAliases.cs | 44 +++++
tests/MyGame/Example/AnyUniqueAliases.cs | 44 +++++
tests/MyGame/Example/ArrayStruct.cs | 6 +
tests/MyGame/Example/ArrayTable.cs | 8 +
tests/MyGame/Example/Color.cs | 1 +
tests/MyGame/Example/Monster.cs | 99 +++++++++++
tests/MyGame/Example/NestedStruct.cs | 4 +
tests/MyGame/Example/Race.cs | 1 +
tests/MyGame/Example/Referrable.cs | 2 +
tests/MyGame/Example/Stat.cs | 3 +
tests/MyGame/Example/Test.cs | 2 +
tests/MyGame/Example/TestEnum.cs | 1 +
.../MyGame/Example/TestSimpleTableWithEnum.cs | 1 +
tests/MyGame/Example/TypeAliases.cs | 12 ++
tests/MyGame/Example/Vec3.cs | 6 +
tests/MyGame/MonsterExtra.cs | 17 ++
tests/generate_code.bat | 11 +-
tests/generate_code.sh | 11 +-
tests/monsterdata_test.json | 4 +-
.../NamespaceA/NamespaceB/EnumInNestedNS.cs | 1 +
.../NamespaceA/NamespaceB/StructInNestedNS.cs | 2 +
.../NamespaceA/NamespaceB/TableInNestedNS.cs | 1 +
.../NamespaceA/SecondTableInA.cs | 1 +
.../NamespaceA/TableInFirstNS.cs | 3 +
tests/namespace_test/NamespaceC/TableInC.cs | 2 +
tests/union_vector/Attacker.cs | 1 +
tests/union_vector/BookReader.cs | 1 +
tests/union_vector/Character.cs | 47 +++++
tests/union_vector/Movie.cs | 38 +++++
tests/union_vector/Rapunzel.cs | 1 +
43 files changed, 680 insertions(+), 42 deletions(-)
create mode 100644 tests/FlatBuffers.Test/Resources/.gitkeep
delete mode 100644 tests/FlatBuffers.Test/Resources/monsterdata_test.mon
create mode 100644 tests/FlatBuffers.Test/packages.config
diff --git a/appveyor.yml b/appveyor.yml
index ee6930f6c..e42fecebd 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -105,6 +105,11 @@ test_script:
# Have to compile this here rather than in "build" above because AppVeyor only
# supports building one project??
- "cd FlatBuffers.Test"
+ - "copy ..\\monsterdata_test.mon Resources\\"
+ - "copy ..\\monsterdata_test.json Resources\\"
+ - "dotnet new sln"
+ - "dotnet sln add FlatBuffers.Test.csproj"
+ - "nuget restore"
- "msbuild.exe /property:Configuration=Release;OutputPath=tempcs /verbosity:minimal FlatBuffers.Test.csproj"
- "tempcs\\FlatBuffers.Test.exe"
# Run tests with UNSAFE_BYTEBUFFER
diff --git a/docs/source/CsharpUsage.md b/docs/source/CsharpUsage.md
index 90a757fb0..f7f585db9 100644
--- a/docs/source/CsharpUsage.md
+++ b/docs/source/CsharpUsage.md
@@ -151,4 +151,25 @@ To use:
fbb.Finish(Monster.Pack(fbb, monsterobj).Value);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+### Json Serialization
+
+An additional feature of the object API is the ability to allow you to
+serialize & deserialize a JSON text.
+To use Json Serialization, add `--gen-json-serializer` option to `flatc` and
+add `Newtonsoft.Json` nuget package to csproj.
+
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cs}
+ // Deserialize MonsterT from json
+ string jsonText = File.ReadAllText(@"Resources/monsterdata_test.json");
+ MonsterT mon = MonsterT.DeserializeFromJson(jsonText);
+
+ // Serialize MonsterT to json
+ string jsonText2 = mon.SerializeToJson();
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+* Limitation
+ * `hash` attribute currentry not supported.
+* NuGet package Dependency
+ * [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json)
+
diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h
index 2e2d46856..dbdacc8e4 100644
--- a/include/flatbuffers/idl.h
+++ b/include/flatbuffers/idl.h
@@ -556,6 +556,7 @@ struct IDLOptions {
std::string root_type;
bool force_defaults;
bool java_primitive_has_method;
+ bool cs_gen_json_serializer;
std::vector cpp_includes;
std::string cpp_std;
std::string proto_namespace_suffix;
@@ -592,12 +593,12 @@ struct IDLOptions {
// for code generation.
unsigned long lang_to_generate;
- // If set (default behavior), empty string fields will be set to nullptr to make
- // the flatbuffer more compact.
+ // If set (default behavior), empty string fields will be set to nullptr to
+ // make the flatbuffer more compact.
bool set_empty_strings_to_null;
- // If set (default behavior), empty vector fields will be set to nullptr to make
- // the flatbuffer more compact.
+ // If set (default behavior), empty vector fields will be set to nullptr to
+ // make the flatbuffer more compact.
bool set_empty_vectors_to_null;
IDLOptions()
@@ -641,6 +642,7 @@ struct IDLOptions {
size_prefixed(false),
force_defaults(false),
java_primitive_has_method(false),
+ cs_gen_json_serializer(false),
lang(IDLOptions::kJava),
mini_reflect(IDLOptions::kNone),
lang_to_generate(0),
@@ -1119,9 +1121,8 @@ bool GenerateJavaGRPC(const Parser &parser, const std::string &path,
// Generate GRPC Python interfaces.
// See idl_gen_grpc.cpp.
-bool GeneratePythonGRPC(const Parser &parser,
- const std::string &path,
- const std::string &file_name);
+bool GeneratePythonGRPC(const Parser &parser, const std::string &path,
+ const std::string &file_name);
} // namespace flatbuffers
diff --git a/src/flatc.cpp b/src/flatc.cpp
index bac3411a8..1be817539 100644
--- a/src/flatc.cpp
+++ b/src/flatc.cpp
@@ -325,7 +325,7 @@ int FlatCompiler::Compile(int argc, const char **argv) {
} else if (arg == "--bfbs-builtins") {
opts.binary_schema_builtins = true;
} else if (arg == "--bfbs-gen-embed") {
- opts.binary_schema_gen_embed= true;
+ opts.binary_schema_gen_embed = true;
} else if (arg == "--no-fb-import") {
opts.skip_flatbuffers_import = true;
} else if (arg == "--no-ts-reexport") {
@@ -348,10 +348,13 @@ int FlatCompiler::Compile(int argc, const char **argv) {
opts.set_empty_vectors_to_null = false;
} else if (arg == "--java-primitive-has-method") {
opts.java_primitive_has_method = true;
+ } else if (arg == "--cs-gen-json-serializer") {
+ opts.cs_gen_json_serializer = true;
} else if (arg == "--flexbuffers") {
opts.use_flexbuffers = true;
- } else if(arg == "--cpp-std") {
- if (++argi >= argc) Error("missing C++ standard specification" + arg, true);
+ } else if (arg == "--cpp-std") {
+ if (++argi >= argc)
+ Error("missing C++ standard specification" + arg, true);
opts.cpp_std = argv[argi];
} else {
for (size_t i = 0; i < params_.num_generators; ++i) {
diff --git a/src/idl_gen_csharp.cpp b/src/idl_gen_csharp.cpp
index a97ae6b01..4bcde6c16 100644
--- a/src/idl_gen_csharp.cpp
+++ b/src/idl_gen_csharp.cpp
@@ -271,6 +271,11 @@ class CSharpGenerator : public BaseGenerator {
// That, and Java Enums are expensive, and not universally liked.
GenComment(enum_def.doc_comment, code_ptr, &comment_config);
+ if (opts.cs_gen_json_serializer && opts.generate_object_based_api) {
+ code +=
+ "[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters."
+ "StringEnumConverter))]\n";
+ }
// In C# this indicates enumeration values can be treated as bit flags.
if (enum_def.attributes.Lookup("bit_flags")) {
code += "[System.FlagsAttribute]\n";
@@ -1274,6 +1279,89 @@ class CSharpGenerator : public BaseGenerator {
code += " }\n";
code += " }\n";
code += "}\n\n";
+ // JsonConverter
+ if (opts.cs_gen_json_serializer) {
+ if (enum_def.attributes.Lookup("private")) {
+ code += "internal ";
+ } else {
+ code += "public ";
+ }
+ code += "class " + union_name +
+ "_JsonConverter : Newtonsoft.Json.JsonConverter {\n";
+ code += " public override bool CanConvert(System.Type objectType) {\n";
+ code += " return objectType == typeof(" + union_name +
+ ") || objectType == typeof(System.Collections.Generic.List<" +
+ union_name + ">);\n";
+ code += " }\n";
+ code +=
+ " public override void WriteJson(Newtonsoft.Json.JsonWriter writer, "
+ "object value, "
+ "Newtonsoft.Json.JsonSerializer serializer) {\n";
+ code += " var _olist = value as System.Collections.Generic.List<" +
+ union_name + ">;\n";
+ code += " if (_olist != null) {\n";
+ code += " writer.WriteStartArray();\n";
+ code +=
+ " foreach (var _o in _olist) { this.WriteJson(writer, _o, "
+ "serializer); }\n";
+ code += " writer.WriteEndArray();\n";
+ code += " } else {\n";
+ code += " this.WriteJson(writer, value as " + union_name +
+ ", serializer);\n";
+ code += " }\n";
+ code += " }\n";
+ code += " public void WriteJson(Newtonsoft.Json.JsonWriter writer, " +
+ union_name +
+ " _o, "
+ "Newtonsoft.Json.JsonSerializer serializer) {\n";
+ code += " if (_o == null) return;\n";
+ code += " serializer.Serialize(writer, _o.Value);\n";
+ code += " }\n";
+ code +=
+ " public override object ReadJson(Newtonsoft.Json.JsonReader "
+ "reader, "
+ "System.Type objectType, "
+ "object existingValue, Newtonsoft.Json.JsonSerializer serializer) "
+ "{\n";
+ code +=
+ " var _olist = existingValue as System.Collections.Generic.List<" +
+ union_name + ">;\n";
+ code += " if (_olist != null) {\n";
+ code += " for (var _j = 0; _j < _olist.Count; ++_j) {\n";
+ code += " reader.Read();\n";
+ code +=
+ " _olist[_j] = this.ReadJson(reader, _olist[_j], "
+ "serializer);\n";
+ code += " }\n";
+ code += " reader.Read();\n";
+ code += " return _olist;\n";
+ code += " } else {\n";
+ code += " return this.ReadJson(reader, existingValue as " +
+ union_name + ", serializer);\n";
+ code += " }\n";
+ code += " }\n";
+ code += " public " + union_name +
+ " ReadJson(Newtonsoft.Json.JsonReader reader, " + union_name +
+ " _o, Newtonsoft.Json.JsonSerializer serializer) {\n";
+ code += " if (_o == null) return null;\n";
+ code += " switch (_o.Type) {\n";
+ for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end();
+ ++it) {
+ auto &ev = **it;
+ if (ev.union_type.base_type == BASE_TYPE_NONE) {
+ code += " default: break;\n";
+ } else {
+ auto type_name = GenTypeGet_ObjectAPI(ev.union_type, opts);
+ code += " case " + enum_def.name + "." + ev.name +
+ ": _o.Value = serializer.Deserialize<" + type_name +
+ ">(reader); break;\n";
+ }
+ }
+ code += " }\n";
+ code += " return _o;\n";
+ code += " }\n";
+ code += "}\n\n";
+ }
}
std::string GenTypeName_ObjectAPI(const std::string &name,
@@ -1794,8 +1882,62 @@ class CSharpGenerator : public BaseGenerator {
if (field.value.type.base_type == BASE_TYPE_UTYPE) continue;
if (field.value.type.element == BASE_TYPE_UTYPE) continue;
auto type_name = GenTypeGet_ObjectAPI(field.value.type, opts);
- code += " public " + type_name + " " + MakeCamel(field.name, true) +
- " { get; set; }\n";
+ auto camel_name = MakeCamel(field.name, true);
+ if (opts.cs_gen_json_serializer) {
+ if (IsUnion(field.value.type)) {
+ auto utype_name = WrapInNameSpace(*field.value.type.enum_def);
+ code +=
+ " [Newtonsoft.Json.JsonProperty(\"" + field.name + "_type\")]\n";
+ if (field.value.type.base_type == BASE_TYPE_VECTOR) {
+ code += " private " + utype_name + "[] " + camel_name + "Type {\n";
+ code += " get {\n";
+ code += " if (this." + camel_name + " == null) return null;\n";
+ code += " var _o = new " + utype_name + "[this." + camel_name +
+ ".Count];\n";
+ code +=
+ " for (var _j = 0; _j < _o.Length; ++_j) { _o[_j] = "
+ "this." +
+ camel_name + "[_j].Type; }\n";
+ code += " return _o;\n";
+ code += " }\n";
+ code += " set {\n";
+ code += " this." + camel_name + " = new List<" + utype_name +
+ "Union>();\n";
+ code += " for (var _j = 0; _j < value.Length; ++_j) {\n";
+ code += " var _o = new " + utype_name + "Union();\n";
+ code += " _o.Type = value[_j];\n";
+ code += " this." + camel_name + ".Add(_o);\n";
+ code += " }\n";
+ code += " }\n";
+ code += " }\n";
+ } else {
+ code += " private " + utype_name + " " + camel_name + "Type {\n";
+ code += " get {\n";
+ code += " return this." + camel_name + " != null ? this." +
+ camel_name + ".Type : " + utype_name + ".NONE;\n";
+ code += " }\n";
+ code += " set {\n";
+ code += " this." + camel_name + " = new " + utype_name +
+ "Union();\n";
+ code += " this." + camel_name + ".Type = value;\n";
+ code += " }\n";
+ code += " }\n";
+ }
+ }
+ code += " [Newtonsoft.Json.JsonProperty(\"" + field.name + "\")]\n";
+ if (IsUnion(field.value.type)) {
+ auto union_name =
+ (field.value.type.base_type == BASE_TYPE_VECTOR)
+ ? GenTypeGet_ObjectAPI(field.value.type.VectorType(), opts)
+ : type_name;
+ code += " [Newtonsoft.Json.JsonConverter(typeof(" + union_name +
+ "_JsonConverter))]\n";
+ }
+ if (field.attributes.Lookup("hash")) {
+ code += " [Newtonsoft.Json.JsonIgnore()]\n";
+ }
+ }
+ code += " public " + type_name + " " + camel_name + " { get; set; }\n";
}
// Generate Constructor
code += "\n";
@@ -1833,6 +1975,21 @@ class CSharpGenerator : public BaseGenerator {
}
}
code += " }\n";
+ // Generate Serialization
+ if (opts.cs_gen_json_serializer &&
+ parser_.root_struct_def_ == &struct_def) {
+ code += "\n";
+ code += " public static " + class_name +
+ " DeserializeFromJson(string jsonText) {\n";
+ code += " return Newtonsoft.Json.JsonConvert.DeserializeObject<" +
+ class_name + ">(jsonText);\n";
+ code += " }\n";
+ code += " public string SerializeToJson() {\n";
+ code +=
+ " return Newtonsoft.Json.JsonConvert.SerializeObject(this, "
+ "Newtonsoft.Json.Formatting.Indented);\n";
+ code += " }\n";
+ }
code += "}\n\n";
}
diff --git a/tests/FlatBuffers.Test/FlatBuffers.Test.csproj b/tests/FlatBuffers.Test/FlatBuffers.Test.csproj
index 3f064b466..c917d2f15 100644
--- a/tests/FlatBuffers.Test/FlatBuffers.Test.csproj
+++ b/tests/FlatBuffers.Test/FlatBuffers.Test.csproj
@@ -17,7 +17,7 @@
full
false
bin\Debug\
- DEBUG;TRACE
+ TRACE;DEBUG
prompt
4
@@ -37,6 +37,9 @@
$(DefineConstants);UNSAFE_BYTEBUFFER
+
+ packages\Newtonsoft.Json.12.0.3\lib\net35\Newtonsoft.Json.dll
+
3.5
@@ -169,6 +172,13 @@
Resources\monsterdata_test.mon
PreserveNewest
+
+ Resources\monsterdata_test.json
+ PreserveNewest
+
+
+
+