From 87d61ba559ba3467c3ecacba9dcaa48b28bf9f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20H=C3=A4ggqvist?= Date: Wed, 20 Dec 2023 23:04:21 +0100 Subject: [PATCH] Support deprecated_readonly in C# json serializer --- src/idl_gen_csharp.cpp | 61 ++++++++++++++++++- tests/KeywordTest/JsonContractResolver.cs | 28 +++++++++ tests/MyGame/Example/ArrayTable.cs | 9 ++- tests/MyGame/Example/JsonContractResolver.cs | 28 +++++++++ tests/MyGame/Example/Monster.cs | 9 ++- tests/MyGame/JsonContractResolver.cs | 28 +++++++++ tests/MyGame/MonsterExtra.cs | 9 ++- .../NamespaceA/JsonContractResolver.cs | 28 +++++++++ .../NamespaceB/JsonContractResolver.cs | 28 +++++++++ .../nested_namespace_test1_generated.cs | 15 +++++ .../nested_namespace_test2_generated.cs | 15 +++++ .../nested_namespace_test3_generated.cs | 15 +++++ .../union_value_collision_generated.cs | 24 +++++++- tests/union_vector/JsonContractResolver.cs | 23 +++++++ tests/union_vector/Movie.cs | 9 ++- 15 files changed, 316 insertions(+), 13 deletions(-) create mode 100644 tests/KeywordTest/JsonContractResolver.cs create mode 100644 tests/MyGame/Example/JsonContractResolver.cs create mode 100644 tests/MyGame/JsonContractResolver.cs create mode 100644 tests/namespace_test/NamespaceA/JsonContractResolver.cs create mode 100644 tests/namespace_test/NamespaceA/NamespaceB/JsonContractResolver.cs create mode 100644 tests/union_vector/JsonContractResolver.cs diff --git a/src/idl_gen_csharp.cpp b/src/idl_gen_csharp.cpp index bfa5992b1..5e7ca1db1 100644 --- a/src/idl_gen_csharp.cpp +++ b/src/idl_gen_csharp.cpp @@ -147,6 +147,21 @@ class CSharpGenerator : public BaseGenerator { std::string one_file_code; cur_name_space_ = parser_.current_namespace_; + if (parser_.opts.cs_gen_json_serializer && + parser_.opts.generate_object_based_api) { + std::string contractresolvercode; + GenJsonContractResolver(&contractresolvercode); + + if (parser_.opts.one_file) { + one_file_code += contractresolvercode; + } else { + if (!SaveType("JsonContractResolver", *parser_.current_namespace_, + contractresolvercode, true, parser_.opts)) { + return false; + } + } + } + for (auto it = parser_.enums_.vec.begin(); it != parser_.enums_.vec.end(); ++it) { std::string enumcode; @@ -2395,6 +2410,9 @@ class CSharpGenerator : public BaseGenerator { auto utype_name = NamespacedName(*field.value.type.enum_def); code += " [Newtonsoft.Json.JsonProperty(\"" + field.name + "_type\")]\n"; + if (field.deprecated == FieldDef::kDeprecatedReadOnly) { + code += " [JsonReadOnly()]\n"; + } if (IsVector(field.value.type)) { code += " private " + utype_name + "[] " + camel_name + "Type {\n"; code += " get {\n"; @@ -2442,6 +2460,8 @@ class CSharpGenerator : public BaseGenerator { } if (field.attributes.Lookup("hash")) { code += " [Newtonsoft.Json.JsonIgnore()]\n"; + } else if (field.deprecated == FieldDef::kDeprecatedReadOnly) { + code += " [JsonReadOnly()]\n"; } } code += " public " + type_name + " " + camel_name + " { get; set; }\n"; @@ -2491,12 +2511,18 @@ class CSharpGenerator : public BaseGenerator { code += " public static " + class_name + " DeserializeFromJson(string jsonText) {\n"; code += " return Newtonsoft.Json.JsonConvert.DeserializeObject<" + - class_name + ">(jsonText);\n"; + class_name + + ">(jsonText, new Newtonsoft.Json.JsonSerializerSettings() {\n"; + code += " ContractResolver = new JsonContractResolver(),\n"; + code += " });\n"; code += " }\n"; code += " public string SerializeToJson() {\n"; code += - " return Newtonsoft.Json.JsonConvert.SerializeObject(this, " - "Newtonsoft.Json.Formatting.Indented);\n"; + " return Newtonsoft.Json.JsonConvert.SerializeObject(this, new " + "Newtonsoft.Json.JsonSerializerSettings() {\n"; + code += " ContractResolver = new JsonContractResolver(),\n"; + code += " Formatting = Newtonsoft.Json.Formatting.Indented,\n"; + code += " });\n"; code += " }\n"; } if (parser_.root_struct_def_ == &struct_def) { @@ -2515,6 +2541,35 @@ class CSharpGenerator : public BaseGenerator { code += "}\n\n"; } + void GenJsonContractResolver(std::string *code_ptr) const { + auto &code = *code_ptr; + code += + "[AttributeUsage(AttributeTargets.Property | " + "AttributeTargets.Field)]\n"; + code += "class JsonReadOnlyAttribute : Attribute {\n"; + code += "}\n\n"; + + code += + "public class JsonContractResolver : " + "Newtonsoft.Json.Serialization.DefaultContractResolver\n"; + code += "{\n"; + code += + " protected override Newtonsoft.Json.Serialization.JsonProperty " + "CreateProperty(System.Reflection.MemberInfo member, " + "Newtonsoft.Json.MemberSerialization memberSerialization)\n"; + code += " {\n"; + code += + " var property = base.CreateProperty(member, " + "memberSerialization);\n"; + code += + " if (Attribute.IsDefined(member, " + "typeof(JsonReadOnlyAttribute)))\n"; + code += " property.Readable = false;\n"; + code += " return property;\n"; + code += " }\n"; + code += "}\n\n"; + } + // This tracks the current namespace used to determine if a type need to be // prefixed by its namespace const Namespace *cur_name_space_; diff --git a/tests/KeywordTest/JsonContractResolver.cs b/tests/KeywordTest/JsonContractResolver.cs new file mode 100644 index 000000000..e94a516cf --- /dev/null +++ b/tests/KeywordTest/JsonContractResolver.cs @@ -0,0 +1,28 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace KeywordTest +{ + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +class JsonReadOnlyAttribute : Attribute { +} + +public class JsonContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver +{ + protected override Newtonsoft.Json.Serialization.JsonProperty CreateProperty(System.Reflection.MemberInfo member, Newtonsoft.Json.MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + if (Attribute.IsDefined(member, typeof(JsonReadOnlyAttribute))) + property.Readable = false; + return property; + } +} + + +} diff --git a/tests/MyGame/Example/ArrayTable.cs b/tests/MyGame/Example/ArrayTable.cs index d688e401e..9daabaee6 100644 --- a/tests/MyGame/Example/ArrayTable.cs +++ b/tests/MyGame/Example/ArrayTable.cs @@ -57,10 +57,15 @@ public class ArrayTableT } public static ArrayTableT DeserializeFromJson(string jsonText) { - return Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText); + return Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText, new Newtonsoft.Json.JsonSerializerSettings() { + ContractResolver = new JsonContractResolver(), + }); } public string SerializeToJson() { - return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); + return Newtonsoft.Json.JsonConvert.SerializeObject(this, new Newtonsoft.Json.JsonSerializerSettings() { + ContractResolver = new JsonContractResolver(), + Formatting = Newtonsoft.Json.Formatting.Indented, + }); } public static ArrayTableT DeserializeFromBinary(byte[] fbBuffer) { return ArrayTable.GetRootAsArrayTable(new ByteBuffer(fbBuffer)).UnPack(); diff --git a/tests/MyGame/Example/JsonContractResolver.cs b/tests/MyGame/Example/JsonContractResolver.cs new file mode 100644 index 000000000..ebf716c7f --- /dev/null +++ b/tests/MyGame/Example/JsonContractResolver.cs @@ -0,0 +1,28 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace MyGame.Example +{ + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +class JsonReadOnlyAttribute : Attribute { +} + +public class JsonContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver +{ + protected override Newtonsoft.Json.Serialization.JsonProperty CreateProperty(System.Reflection.MemberInfo member, Newtonsoft.Json.MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + if (Attribute.IsDefined(member, typeof(JsonReadOnlyAttribute))) + property.Readable = false; + return property; + } +} + + +} diff --git a/tests/MyGame/Example/Monster.cs b/tests/MyGame/Example/Monster.cs index fb4c9e744..a8e6752d4 100644 --- a/tests/MyGame/Example/Monster.cs +++ b/tests/MyGame/Example/Monster.cs @@ -1085,10 +1085,15 @@ public class MonsterT } public static MonsterT DeserializeFromJson(string jsonText) { - return Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText); + return Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText, new Newtonsoft.Json.JsonSerializerSettings() { + ContractResolver = new JsonContractResolver(), + }); } public string SerializeToJson() { - return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); + return Newtonsoft.Json.JsonConvert.SerializeObject(this, new Newtonsoft.Json.JsonSerializerSettings() { + ContractResolver = new JsonContractResolver(), + Formatting = Newtonsoft.Json.Formatting.Indented, + }); } public static MonsterT DeserializeFromBinary(byte[] fbBuffer) { return Monster.GetRootAsMonster(new ByteBuffer(fbBuffer)).UnPack(); diff --git a/tests/MyGame/JsonContractResolver.cs b/tests/MyGame/JsonContractResolver.cs new file mode 100644 index 000000000..099573ce8 --- /dev/null +++ b/tests/MyGame/JsonContractResolver.cs @@ -0,0 +1,28 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace MyGame +{ + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +class JsonReadOnlyAttribute : Attribute { +} + +public class JsonContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver +{ + protected override Newtonsoft.Json.Serialization.JsonProperty CreateProperty(System.Reflection.MemberInfo member, Newtonsoft.Json.MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + if (Attribute.IsDefined(member, typeof(JsonReadOnlyAttribute))) + property.Readable = false; + return property; + } +} + + +} diff --git a/tests/MyGame/MonsterExtra.cs b/tests/MyGame/MonsterExtra.cs index 63a689a31..4b0f8de01 100644 --- a/tests/MyGame/MonsterExtra.cs +++ b/tests/MyGame/MonsterExtra.cs @@ -191,10 +191,15 @@ public class MonsterExtraT } public static MonsterExtraT DeserializeFromJson(string jsonText) { - return Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText); + return Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText, new Newtonsoft.Json.JsonSerializerSettings() { + ContractResolver = new JsonContractResolver(), + }); } public string SerializeToJson() { - return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); + return Newtonsoft.Json.JsonConvert.SerializeObject(this, new Newtonsoft.Json.JsonSerializerSettings() { + ContractResolver = new JsonContractResolver(), + Formatting = Newtonsoft.Json.Formatting.Indented, + }); } public static MonsterExtraT DeserializeFromBinary(byte[] fbBuffer) { return MonsterExtra.GetRootAsMonsterExtra(new ByteBuffer(fbBuffer)).UnPack(); diff --git a/tests/namespace_test/NamespaceA/JsonContractResolver.cs b/tests/namespace_test/NamespaceA/JsonContractResolver.cs new file mode 100644 index 000000000..519943238 --- /dev/null +++ b/tests/namespace_test/NamespaceA/JsonContractResolver.cs @@ -0,0 +1,28 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace NamespaceA +{ + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +class JsonReadOnlyAttribute : Attribute { +} + +public class JsonContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver +{ + protected override Newtonsoft.Json.Serialization.JsonProperty CreateProperty(System.Reflection.MemberInfo member, Newtonsoft.Json.MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + if (Attribute.IsDefined(member, typeof(JsonReadOnlyAttribute))) + property.Readable = false; + return property; + } +} + + +} diff --git a/tests/namespace_test/NamespaceA/NamespaceB/JsonContractResolver.cs b/tests/namespace_test/NamespaceA/NamespaceB/JsonContractResolver.cs new file mode 100644 index 000000000..6aedab71a --- /dev/null +++ b/tests/namespace_test/NamespaceA/NamespaceB/JsonContractResolver.cs @@ -0,0 +1,28 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +namespace NamespaceA.NamespaceB +{ + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +class JsonReadOnlyAttribute : Attribute { +} + +public class JsonContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver +{ + protected override Newtonsoft.Json.Serialization.JsonProperty CreateProperty(System.Reflection.MemberInfo member, Newtonsoft.Json.MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + if (Attribute.IsDefined(member, typeof(JsonReadOnlyAttribute))) + property.Readable = false; + return property; + } +} + + +} diff --git a/tests/nested_namespace_test/nested_namespace_test1_generated.cs b/tests/nested_namespace_test/nested_namespace_test1_generated.cs index f1e646c42..f9eee17ee 100644 --- a/tests/nested_namespace_test/nested_namespace_test1_generated.cs +++ b/tests/nested_namespace_test/nested_namespace_test1_generated.cs @@ -9,6 +9,21 @@ using global::System; using global::System.Collections.Generic; using global::Google.FlatBuffers; +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +class JsonReadOnlyAttribute : Attribute { +} + +public class JsonContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver +{ + protected override Newtonsoft.Json.Serialization.JsonProperty CreateProperty(System.Reflection.MemberInfo member, Newtonsoft.Json.MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + if (Attribute.IsDefined(member, typeof(JsonReadOnlyAttribute))) + property.Readable = false; + return property; + } +} + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] public enum Color : sbyte { diff --git a/tests/nested_namespace_test/nested_namespace_test2_generated.cs b/tests/nested_namespace_test/nested_namespace_test2_generated.cs index efd2584dd..c545459c9 100644 --- a/tests/nested_namespace_test/nested_namespace_test2_generated.cs +++ b/tests/nested_namespace_test/nested_namespace_test2_generated.cs @@ -9,6 +9,21 @@ using global::System; using global::System.Collections.Generic; using global::Google.FlatBuffers; +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +class JsonReadOnlyAttribute : Attribute { +} + +public class JsonContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver +{ + protected override Newtonsoft.Json.Serialization.JsonProperty CreateProperty(System.Reflection.MemberInfo member, Newtonsoft.Json.MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + if (Attribute.IsDefined(member, typeof(JsonReadOnlyAttribute))) + property.Readable = false; + return property; + } +} + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] public enum Color : sbyte { diff --git a/tests/nested_namespace_test/nested_namespace_test3_generated.cs b/tests/nested_namespace_test/nested_namespace_test3_generated.cs index 32be9bede..0f95186c5 100644 --- a/tests/nested_namespace_test/nested_namespace_test3_generated.cs +++ b/tests/nested_namespace_test/nested_namespace_test3_generated.cs @@ -9,6 +9,21 @@ using global::System; using global::System.Collections.Generic; using global::Google.FlatBuffers; +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +class JsonReadOnlyAttribute : Attribute { +} + +public class JsonContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver +{ + protected override Newtonsoft.Json.Serialization.JsonProperty CreateProperty(System.Reflection.MemberInfo member, Newtonsoft.Json.MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + if (Attribute.IsDefined(member, typeof(JsonReadOnlyAttribute))) + property.Readable = false; + return property; + } +} + public struct ColorTestTable : IFlatbufferObject { private Table __p; diff --git a/tests/union_value_collsion/union_value_collision_generated.cs b/tests/union_value_collsion/union_value_collision_generated.cs index e741e9afb..81009e451 100644 --- a/tests/union_value_collsion/union_value_collision_generated.cs +++ b/tests/union_value_collsion/union_value_collision_generated.cs @@ -9,6 +9,21 @@ using global::System; using global::System.Collections.Generic; using global::Google.FlatBuffers; +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +class JsonReadOnlyAttribute : Attribute { +} + +public class JsonContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver +{ + protected override Newtonsoft.Json.Serialization.JsonProperty CreateProperty(System.Reflection.MemberInfo member, Newtonsoft.Json.MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + if (Attribute.IsDefined(member, typeof(JsonReadOnlyAttribute))) + property.Readable = false; + return property; + } +} + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] public enum Value : byte { @@ -498,10 +513,15 @@ public class CollisionT } public static CollisionT DeserializeFromJson(string jsonText) { - return Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText); + return Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText, new Newtonsoft.Json.JsonSerializerSettings() { + ContractResolver = new JsonContractResolver(), + }); } public string SerializeToJson() { - return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); + return Newtonsoft.Json.JsonConvert.SerializeObject(this, new Newtonsoft.Json.JsonSerializerSettings() { + ContractResolver = new JsonContractResolver(), + Formatting = Newtonsoft.Json.Formatting.Indented, + }); } public static CollisionT DeserializeFromBinary(byte[] fbBuffer) { return Collision.GetRootAsCollision(new ByteBuffer(fbBuffer)).UnPack(); diff --git a/tests/union_vector/JsonContractResolver.cs b/tests/union_vector/JsonContractResolver.cs new file mode 100644 index 000000000..ea8209240 --- /dev/null +++ b/tests/union_vector/JsonContractResolver.cs @@ -0,0 +1,23 @@ +// +// automatically generated by the FlatBuffers compiler, do not modify +// + +using global::System; +using global::System.Collections.Generic; +using global::Google.FlatBuffers; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +class JsonReadOnlyAttribute : Attribute { +} + +public class JsonContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver +{ + protected override Newtonsoft.Json.Serialization.JsonProperty CreateProperty(System.Reflection.MemberInfo member, Newtonsoft.Json.MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + if (Attribute.IsDefined(member, typeof(JsonReadOnlyAttribute))) + property.Readable = false; + return property; + } +} + diff --git a/tests/union_vector/Movie.cs b/tests/union_vector/Movie.cs index 8e75f7c53..cf292fc92 100644 --- a/tests/union_vector/Movie.cs +++ b/tests/union_vector/Movie.cs @@ -197,10 +197,15 @@ public class MovieT } public static MovieT DeserializeFromJson(string jsonText) { - return Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText); + return Newtonsoft.Json.JsonConvert.DeserializeObject(jsonText, new Newtonsoft.Json.JsonSerializerSettings() { + ContractResolver = new JsonContractResolver(), + }); } public string SerializeToJson() { - return Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); + return Newtonsoft.Json.JsonConvert.SerializeObject(this, new Newtonsoft.Json.JsonSerializerSettings() { + ContractResolver = new JsonContractResolver(), + Formatting = Newtonsoft.Json.Formatting.Indented, + }); } public static MovieT DeserializeFromBinary(byte[] fbBuffer) { return Movie.GetRootAsMovie(new ByteBuffer(fbBuffer)).UnPack();