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 + + + +