From ce51dea29d9fa667aa97b5a72d717de8e8b29765 Mon Sep 17 00:00:00 2001 From: Richard Marcus <richard.marcus@fau.de> Date: Fri, 8 Jan 2021 14:50:28 +0100 Subject: [PATCH] added jsonsubtypes as plain folder --- Assets/Plugins/JsonSubTypes/.editorconfig | 15 + Assets/Plugins/JsonSubTypes/.gitignore | 13 + Assets/Plugins/JsonSubTypes/CHANGELOG.md | 83 +++ Assets/Plugins/JsonSubTypes/CHANGELOG.md.meta | 7 + .../Plugins/JsonSubTypes/CODE_OF_CONDUCT.md | 46 ++ .../JsonSubTypes/CODE_OF_CONDUCT.md.meta | 7 + Assets/Plugins/JsonSubTypes/JsonSubTypes.meta | 8 + .../JsonSubTypes/JsonSubTypes.sln.meta | 7 + .../JsonSubTypes/JsonSubTypes.Key.snk | Bin 0 -> 596 bytes .../JsonSubTypes/JsonSubTypes.Key.snk.meta | 7 + .../JsonSubTypes/JsonSubTypes.csproj.meta | 7 + .../JsonSubTypes/JsonSubTypes/JsonSubtypes.cs | 475 ++++++++++++++++++ .../JsonSubTypes/JsonSubtypes.cs.meta | 11 + ...onSubtypesByDiscriminatorValueConverter.cs | 125 +++++ ...typesByDiscriminatorValueConverter.cs.meta | 11 + ...JsonSubtypesByPropertyPresenceConverter.cs | 20 + ...ubtypesByPropertyPresenceConverter.cs.meta | 11 + .../JsonSubTypes/JsonSubtypesConverter.cs | 54 ++ .../JsonSubtypesConverter.cs.meta | 11 + .../JsonSubtypesConverterBuilder.cs | 91 ++++ .../JsonSubtypesConverterBuilder.cs.meta | 11 + ...sonSubtypesWithPropertyConverterBuilder.cs | 55 ++ ...btypesWithPropertyConverterBuilder.cs.meta | 11 + .../JsonSubTypes/NullableDictionary.cs | 65 +++ .../JsonSubTypes/NullableDictionary.cs.meta | 11 + .../JsonSubTypes/JsonSubTypes/packages.config | 4 + .../JsonSubTypes/packages.config.meta | 32 ++ Assets/Plugins/JsonSubTypes/LICENSE | 21 + Assets/Plugins/JsonSubTypes/LICENSE.meta | 7 + Assets/Plugins/JsonSubTypes/README.md | 234 +++++++++ Assets/Plugins/JsonSubTypes/README.md.meta | 7 + Assets/Plugins/JsonSubTypes/_config.yml | 1 + Assets/Plugins/JsonSubTypes/_config.yml.meta | 7 + Assets/Plugins/JsonSubTypes/appveyor.yml | 57 +++ Assets/Plugins/JsonSubTypes/appveyor.yml.meta | 7 + 35 files changed, 1539 insertions(+) create mode 100644 Assets/Plugins/JsonSubTypes/.editorconfig create mode 100644 Assets/Plugins/JsonSubTypes/.gitignore create mode 100644 Assets/Plugins/JsonSubTypes/CHANGELOG.md create mode 100644 Assets/Plugins/JsonSubTypes/CHANGELOG.md.meta create mode 100644 Assets/Plugins/JsonSubTypes/CODE_OF_CONDUCT.md create mode 100644 Assets/Plugins/JsonSubTypes/CODE_OF_CONDUCT.md.meta create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes.meta create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes.sln.meta create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubTypes.Key.snk create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubTypes.Key.snk.meta create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubTypes.csproj.meta create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypes.cs create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypes.cs.meta create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesByDiscriminatorValueConverter.cs create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesByDiscriminatorValueConverter.cs.meta create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesByPropertyPresenceConverter.cs create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesByPropertyPresenceConverter.cs.meta create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesConverter.cs create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesConverter.cs.meta create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesConverterBuilder.cs create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesConverterBuilder.cs.meta create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesWithPropertyConverterBuilder.cs create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesWithPropertyConverterBuilder.cs.meta create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/NullableDictionary.cs create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/NullableDictionary.cs.meta create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/packages.config create mode 100644 Assets/Plugins/JsonSubTypes/JsonSubTypes/packages.config.meta create mode 100644 Assets/Plugins/JsonSubTypes/LICENSE create mode 100644 Assets/Plugins/JsonSubTypes/LICENSE.meta create mode 100644 Assets/Plugins/JsonSubTypes/README.md create mode 100644 Assets/Plugins/JsonSubTypes/README.md.meta create mode 100644 Assets/Plugins/JsonSubTypes/_config.yml create mode 100644 Assets/Plugins/JsonSubTypes/_config.yml.meta create mode 100644 Assets/Plugins/JsonSubTypes/appveyor.yml create mode 100644 Assets/Plugins/JsonSubTypes/appveyor.yml.meta diff --git a/Assets/Plugins/JsonSubTypes/.editorconfig b/Assets/Plugins/JsonSubTypes/.editorconfig new file mode 100644 index 00000000..41b05d43 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/.editorconfig @@ -0,0 +1,15 @@ +############################### +# Core EditorConfig Options # +############################### + +root = true + +# All files +[*] +indent_style = space + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8-bom diff --git a/Assets/Plugins/JsonSubTypes/.gitignore b/Assets/Plugins/JsonSubTypes/.gitignore new file mode 100644 index 00000000..981cf383 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/.gitignore @@ -0,0 +1,13 @@ +/packages +*.suo +bin +obj +*.user +/TestResults +*.ide +/.idea +/.vs + + +# Local History for Visual Studio +.localhistory/ \ No newline at end of file diff --git a/Assets/Plugins/JsonSubTypes/CHANGELOG.md b/Assets/Plugins/JsonSubTypes/CHANGELOG.md new file mode 100644 index 00000000..c10de7c8 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/CHANGELOG.md @@ -0,0 +1,83 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.7.0] - 2020-03-28 + +### Added +- Fallback to JSONPath to allow nested field as a deserialization property. #89 +- Bump Newtonsoft.Json from 11.0.2 to 12.0.3 #88 +- Implements dynamic registration for subtype detection by property presence. #50 + +### Fixed +- JsonSubtypes does not respect naming strategy for discriminator property value #80 +- Fix infinite loop when specifying name of abstract base class as discriminator #83 +- Serializing base class with discriminator property results in KeyNotFoundException #79 + +## [1.6.0] - 2019-06-25 +### Added +- Support for multiple discriminators on single type #66 +- Support for per inheritance level discriminators #60 +- Support specifying a falback sub type if none matched #63 +- Provide NuGet package with strong name #75 +- Changelog history and documentation arround versionning + +## [1.5.2] - 2019-01-19 +### Security +- Arbitrary constructor invocation #56 + +## [1.5.1] - 2018-10-15 +### Fixed +- Read.me was imported by the nuget install #51 + +## [1.5.0] - 2018-08-27 +### Added +- Ability to set the discriminator property order to first (see #46) +- Compatibility with JSON.NET 11.0.2 (see #47) + +## [1.4.0] - 2018-04-18 +### Added +- Support for both camel case and non camel case parameters #31 +- Explicit support for netstandard2.0 #34 + +### Fixed +- Code refactoring to reduce the number of conditional compilation statements #36 + +## [1.3.1] - 2014-04-12 +### Fixed +- fixed exception that was returned instead of thrown #32 + +## [1.3.0] - 2018-29-01 +### Added +- De-/Serialization for sub-types without "type" property #13 +- Option for avoiding mapping on the Parent #26 + +### Fixed +- Sonar (Coverage) analysis is broken #23 + +## [1.1.3] - 2017-11-15 +### Fixed +- fixed support of framework net40 #21 + +## [1.1.2] - 2017-11-20 +### Fixed +- fix #18 : Deserialisation is not thread safe + +## [1.1.1] - 2017-09-22 +### Fixed +- fix #11 Nuget packages doesn't work for .Net Framework projects + +## [1.1.0] - 2017-09-19 +### Added +- Parse string enum values #9. + +## [1.0.0] - 2017-07-23 +Initial release ! + + + + diff --git a/Assets/Plugins/JsonSubTypes/CHANGELOG.md.meta b/Assets/Plugins/JsonSubTypes/CHANGELOG.md.meta new file mode 100644 index 00000000..cefbb9ea --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/CHANGELOG.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8dbd04d1390861e459e0fdba662dec7a +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/JsonSubTypes/CODE_OF_CONDUCT.md b/Assets/Plugins/JsonSubTypes/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..7e77a3a2 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/Assets/Plugins/JsonSubTypes/CODE_OF_CONDUCT.md.meta b/Assets/Plugins/JsonSubTypes/CODE_OF_CONDUCT.md.meta new file mode 100644 index 00000000..5001ddb3 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/CODE_OF_CONDUCT.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2f8b79b30e1066e4fa55fc7d1d73610d +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes.meta b/Assets/Plugins/JsonSubTypes/JsonSubTypes.meta new file mode 100644 index 00000000..4d0b776c --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 78aa215ce90c3b04eb6cf8d4e4f1b73c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes.sln.meta b/Assets/Plugins/JsonSubTypes/JsonSubTypes.sln.meta new file mode 100644 index 00000000..24c776a3 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes.sln.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f4c875c153339654d94453d5b4bf7da5 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubTypes.Key.snk b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubTypes.Key.snk new file mode 100644 index 0000000000000000000000000000000000000000..f749d533ae3b2498f43113e3f3990330d71b2533 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096|CfmHzp5u^>Nqc?ePk*To;pH5&^t_|l z3%64UP<(Z=+#iEmwj5UMnYze}ttFX+F@blsfP)T6TICkc{MH!yXLTom3VFS?gFhlp zYpK*p**Rko3qpvH#U!hE_{=60pS_>Sk)!)aQ-S+c6P4#T@hS3S1$B&Ka=lW6xxBr* z1@v0m<Sn(Nzt`EGM8;uQi|x8I7j<qTrd4Sz`tp4=CAYC@eD3W>4`^qjuLed4{5jZ< zVr#G4Hw8do@@&9kUAUy*=b{WQMY2&9&02!q-zo~${3ZDFhv(F(>g)?^e7#$`jK6E! zn<A^?;VUqgo>%n)TZtm+1=gx#W^DYE2OBIo5*3Q3!l@Ej@Baqe->}1qi^h&J1zHql z*GUC($45A>>&`yH;2}hIhwgGCebc~TFiqgvF&opqD<5XtggOVSB(*eG5sBC;9h-0h z;1KTda`kwc+8CzI<_h>RL{;rslF=z-T+1Qxq)(5>LX*dG__MYE4{<~Y3w_gTw?@Tb zLKIP*rc4o=ZO>aB3G&nEvNekT*=IX2JUuXGJiI0LU`{v)bSiM+GVW+7w91Dh;fkB4 zX2!gp5I7+5qfwE00b!>=*WG6fr!Fd$U@d2Tkb3udqLB4we^KkpR!OWN@g|Ge&?a4$ zg_#gCW9MHV`J5;Yr-PK7sVYm^&cDKbR^8OZ<{f`Hfrf4NYIN8;o1ndvR8gpH#L}s< iI!7%4t}Ydb?4D=`QCYufxA2)cNtZypoCLqeciuz<p(5b` literal 0 HcmV?d00001 diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubTypes.Key.snk.meta b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubTypes.Key.snk.meta new file mode 100644 index 00000000..9efc0b68 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubTypes.Key.snk.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 76648f4d8027077458aedbb4c7a56799 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubTypes.csproj.meta b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubTypes.csproj.meta new file mode 100644 index 00000000..9a52a10e --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubTypes.csproj.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1e3c4601db6cf084dbedfe8f72a5d693 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypes.cs b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypes.cs new file mode 100644 index 00000000..296d3025 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypes.cs @@ -0,0 +1,475 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +#if (NET35 || NET40) +using TypeInfo = System.Type; +#else +using System.Reflection; +#endif + +namespace JsonSubTypes +{ + // MIT License + // + // Copyright (c) 2017 Emmanuel Counasse + // + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to deal + // in the Software without restriction, including without limitation the rights + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + // copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + // + // The above copyright notice and this permission notice shall be included in all + // copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + // SOFTWARE. + + public class JsonSubtypes : JsonConverter + { + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true)] + public class KnownSubTypeAttribute : Attribute + { + public Type SubType { get; } + public object AssociatedValue { get; } + + public KnownSubTypeAttribute(Type subType, object associatedValue) + { + SubType = subType; + AssociatedValue = associatedValue; + } + } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] + public class FallBackSubTypeAttribute : Attribute + { + public Type SubType { get; } + + public FallBackSubTypeAttribute(Type subType) + { + SubType = subType; + } + } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true)] + public class KnownSubTypeWithPropertyAttribute : Attribute + { + public Type SubType { get; } + public string PropertyName { get; } + + public KnownSubTypeWithPropertyAttribute(Type subType, string propertyName) + { + SubType = subType; + PropertyName = propertyName; + } + } + + protected readonly string JsonDiscriminatorPropertyName; + + [ThreadStatic] private static bool _isInsideRead; + + [ThreadStatic] private static JsonReader _reader; + + public override bool CanRead + { + get + { + if (!_isInsideRead) + return true; + + return !string.IsNullOrEmpty(_reader.Path); + } + } + + public override bool CanWrite => false; + + public JsonSubtypes() + { + } + + public JsonSubtypes(string jsonDiscriminatorPropertyName) + { + JsonDiscriminatorPropertyName = jsonDiscriminatorPropertyName; + } + + public override bool CanConvert(Type objectType) + { + return false; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) + { + return ReadJson(reader, objectType, serializer); + } + + private object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer) + { + while (reader.TokenType == JsonToken.Comment) + reader.Read(); + + object value; + switch (reader.TokenType) + { + case JsonToken.Null: + value = null; + break; + case JsonToken.StartObject: + value = ReadObject(reader, objectType, serializer); + break; + case JsonToken.StartArray: + { + var elementType = GetElementType(objectType); + if (elementType == null) + { + throw CreateJsonReaderException(reader, $"Impossible to read JSON array to fill type: {objectType.Name}"); + } + value = ReadArray(reader, objectType, elementType, serializer); + break; + } + default: + throw CreateJsonReaderException(reader, $"Unrecognized token: {reader.TokenType}"); + } + + return value; + } + + private static JsonReaderException CreateJsonReaderException(JsonReader reader, string message) + { + var lineNumber = 0; + var linePosition = 0; + if (reader is IJsonLineInfo lineInfo && lineInfo.HasLineInfo()) + { + lineNumber = lineInfo.LineNumber; + linePosition = lineInfo.LinePosition; + } + + return new JsonReaderException(message, reader.Path, lineNumber, linePosition, null); + } + + private IList ReadArray(JsonReader reader, Type targetType, Type elementType, JsonSerializer serializer) + { + var list = CreateCompatibleList(targetType, elementType); + while (reader.Read() && reader.TokenType != JsonToken.EndArray) + { + list.Add(ReadJson(reader, elementType, serializer)); + } + + if (!targetType.IsArray) + return list; + + var array = Array.CreateInstance(elementType, list.Count); + list.CopyTo(array, 0); + return array; + } + + private static IList CreateCompatibleList(Type targetContainerType, Type elementType) + { + var typeInfo = ToTypeInfo(targetContainerType); + if (typeInfo.IsArray || typeInfo.IsAbstract) + { + return (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType)); + } + + return (IList)Activator.CreateInstance(targetContainerType); + } + + private static Type GetElementType(Type arrayOrGenericContainer) + { + if (arrayOrGenericContainer.IsArray) + { + return arrayOrGenericContainer.GetElementType(); + } + + var genericTypeArguments = GetGenericTypeArguments(arrayOrGenericContainer); + return genericTypeArguments.FirstOrDefault(); + } + + private object ReadObject(JsonReader reader, Type objectType, JsonSerializer serializer) + { + var jObject = JObject.Load(reader); + + var targetType = GetType(jObject, objectType, serializer) ?? objectType; + + return ThreadStaticReadObject(reader, serializer, jObject, targetType); + } + + private static JsonReader CreateAnotherReader(JToken jToken, JsonReader reader) + { + var jObjectReader = jToken.CreateReader(); + jObjectReader.Culture = reader.Culture; + jObjectReader.CloseInput = reader.CloseInput; + jObjectReader.SupportMultipleContent = reader.SupportMultipleContent; + jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling; + jObjectReader.FloatParseHandling = reader.FloatParseHandling; + jObjectReader.DateFormatString = reader.DateFormatString; + jObjectReader.DateParseHandling = reader.DateParseHandling; + return jObjectReader; + } + + private Type ResolveType(JObject jObject, Type parentType, JsonSerializer serializer) + { + Type resolvedType; + if (JsonDiscriminatorPropertyName == null) + { + resolvedType = GetTypeByPropertyPresence(jObject, parentType); + } + else + { + resolvedType = GetTypeFromDiscriminatorValue(jObject, parentType, serializer); + } + + return resolvedType ?? GetFallbackSubType(parentType); + } + + private Type GetType(JObject jObject, Type parentType, JsonSerializer serializer) + { + Type targetType = parentType; + JsonSubtypes lastTypeResolver = null; + JsonSubtypes currentTypeResolver = this; + var visitedTypes = new HashSet<Type> { targetType }; + + var jsonConverterCollection = serializer.Converters.OfType<JsonSubtypesByDiscriminatorValueConverter>().ToList(); + while (currentTypeResolver != null && currentTypeResolver != lastTypeResolver) + { + targetType = currentTypeResolver.ResolveType(jObject, targetType, serializer); + if (!visitedTypes.Add(targetType)) + { + break; + } + + lastTypeResolver = currentTypeResolver; + jsonConverterCollection = jsonConverterCollection.Where(c => c != currentTypeResolver).ToList(); + currentTypeResolver = GetTypeResolver(ToTypeInfo(targetType), jsonConverterCollection); + } + + return targetType; + } + + private JsonSubtypes GetTypeResolver(TypeInfo targetType, IEnumerable<JsonSubtypesByDiscriminatorValueConverter> jsonConverterCollection) + { + if (targetType == null) + { + return null; + } + + var jsonConverterAttribute = GetAttribute<JsonConverterAttribute>(targetType); + if (jsonConverterAttribute != null && ToTypeInfo(typeof(JsonSubtypes)).IsAssignableFrom(ToTypeInfo(jsonConverterAttribute.ConverterType))) + { + return (JsonSubtypes)Activator.CreateInstance(jsonConverterAttribute.ConverterType, jsonConverterAttribute.ConverterParameters); + } + + return jsonConverterCollection + .FirstOrDefault(c => c.CanConvert(ToType(targetType))); + } + + private Type GetTypeByPropertyPresence(JObject jObject, Type parentType) + { + var knownSubTypeAttributes = GetTypesByPropertyPresence(parentType); + + var types = knownSubTypeAttributes + .Select(knownType => + { + if (TryGetValueInJson(jObject, knownType.Key, out JToken _)) + return knownType.Value; + + var token = jObject.SelectToken(knownType.Key); + if (token != null) + { + return knownType.Value; + } + + return null; + }) + .Where(type => type != null) + .ToArray(); + + if (types.Length == 1) + { + return types[0]; + } + + throw new JsonSerializationException("Ambiguous type resolution, expected only one type but got: " + String.Join(", ", types.Select(t => t.FullName).ToArray())); + } + + internal virtual Dictionary<string, Type> GetTypesByPropertyPresence(Type parentType) + { + return GetAttributes<KnownSubTypeWithPropertyAttribute>(ToTypeInfo(parentType)) + .ToDictionary(a => a.PropertyName, a => a.SubType); + } + + private Type GetTypeFromDiscriminatorValue(JObject jObject, Type parentType, JsonSerializer serializer) + { + if (!TryGetValueInJson(jObject, JsonDiscriminatorPropertyName, out var discriminatorValue)) + { + discriminatorValue = jObject.SelectToken(JsonDiscriminatorPropertyName); + } + + if (discriminatorValue == null) + { + return null; + } + + var typeMapping = GetSubTypeMapping(parentType); + if (typeMapping.Entries().Any()) + { + return GetTypeFromMapping(typeMapping, discriminatorValue, serializer); + } + + return GetTypeByName(discriminatorValue.Value<string>(), ToTypeInfo(parentType)); + } + + private static bool TryGetValueInJson(IDictionary<string, JToken> jObject, string propertyName, out JToken value) + { + if (jObject.TryGetValue(propertyName, out value)) + { + return true; + } + + var matchingProperty = jObject + .Keys + .FirstOrDefault(jsonProperty => string.Equals(jsonProperty, propertyName, StringComparison.OrdinalIgnoreCase)); + + if (matchingProperty == null) + { + return false; + } + + value = jObject[matchingProperty]; + return true; + } + + private static Type GetTypeByName(string typeName, TypeInfo parentType) + { + if (typeName == null) + { + return null; + } + + var insideAssembly = parentType.Assembly; + + var parentTypeFullName = parentType.FullName; + + var typeByName = insideAssembly.GetType(typeName); + if (parentTypeFullName != null && typeByName == null) + { + var searchLocation = parentTypeFullName.Substring(0, parentTypeFullName.Length - parentType.Name.Length); + typeByName = insideAssembly.GetType(searchLocation + typeName, false, true); + } + + var typeByNameInfo = ToTypeInfo(typeByName); + if (typeByNameInfo != null && parentType.IsAssignableFrom(typeByNameInfo)) + { + return typeByName; + } + + return null; + } + + private static Type GetTypeFromMapping(NullableDictionary<object, Type> typeMapping, JToken discriminatorToken, JsonSerializer serializer) + { + if (discriminatorToken.Type == JTokenType.Null) + { + typeMapping.TryGetValue(null, out Type targetType); + + return targetType; + } + + var key = typeMapping.NotNullKeys().FirstOrDefault(); + if (key != null) + { + var targetLookupValueType = key.GetType(); + var lookupValue = discriminatorToken.ToObject(targetLookupValueType, serializer); + + if (typeMapping.TryGetValue(lookupValue, out Type targetType)) + { + return targetType; + } + } + + return null; + } + + internal virtual NullableDictionary<object, Type> GetSubTypeMapping(Type type) + { + var dictionary = new NullableDictionary<object, Type>(); + + GetAttributes<KnownSubTypeAttribute>(ToTypeInfo(type)) + .ToList() + .ForEach(x => dictionary.Add(x.AssociatedValue, x.SubType)); + + return dictionary; + } + + internal virtual Type GetFallbackSubType(Type type) + { + return GetAttribute<FallBackSubTypeAttribute>(ToTypeInfo(type))?.SubType; + } + + private static object ThreadStaticReadObject(JsonReader reader, JsonSerializer serializer, JToken jToken, Type targetType) + { + _reader = CreateAnotherReader(jToken, reader); + _isInsideRead = true; + try + { + return serializer.Deserialize(_reader, targetType); + } + finally + { + _isInsideRead = false; + } + } + + private static IEnumerable<T> GetAttributes<T>(TypeInfo typeInfo) where T : Attribute + { + return typeInfo.GetCustomAttributes(false) + .OfType<T>(); + } + + private static T GetAttribute<T>(TypeInfo typeInfo) where T : Attribute + { + return GetAttributes<T>(typeInfo).FirstOrDefault(); + } + + private static IEnumerable<Type> GetGenericTypeArguments(Type type) + { +#if (NET35 || NET40) + return type.GetGenericArguments(); +#else + return type.GenericTypeArguments; +#endif + } + + internal static TypeInfo ToTypeInfo(Type type) + { +#if (NET35 || NET40) + return type; +#else + return type?.GetTypeInfo(); +#endif + } + + internal static Type ToType(TypeInfo typeInfo) + { +#if (NET35 || NET40) + return typeInfo; +#else + return typeInfo?.AsType(); +#endif + } + } +} diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypes.cs.meta b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypes.cs.meta new file mode 100644 index 00000000..b575fd92 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1e427f6487939d44fa7981135e87a7e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesByDiscriminatorValueConverter.cs b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesByDiscriminatorValueConverter.cs new file mode 100644 index 00000000..7922cf96 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesByDiscriminatorValueConverter.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace JsonSubTypes +{ + // MIT License + // + // Copyright (c) 2017 Emmanuel Counasse + // + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to deal + // in the Software without restriction, including without limitation the rights + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + // copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + // + // The above copyright notice and this permission notice shall be included in all + // copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + // SOFTWARE. + + internal class JsonSubtypesByDiscriminatorValueConverter : JsonSubtypesConverter + { + [ThreadStatic] private static bool _isInsideWrite; + [ThreadStatic] private static bool _allowNextWrite; + + private readonly bool _addDiscriminatorFirst; + private readonly bool _serializeDiscriminatorProperty; + private readonly Dictionary<Type, object> _supportedTypes = new Dictionary<Type, object>(); + private readonly NullableDictionary<object, Type> _subTypeMapping; + + internal JsonSubtypesByDiscriminatorValueConverter(Type baseType, string discriminatorProperty, + NullableDictionary<object, Type> subTypeMapping, bool serializeDiscriminatorProperty, bool addDiscriminatorFirst, Type fallbackType) : base(baseType, discriminatorProperty, fallbackType) + { + _serializeDiscriminatorProperty = serializeDiscriminatorProperty; + _subTypeMapping = subTypeMapping; + _addDiscriminatorFirst = addDiscriminatorFirst; + foreach (var type in _subTypeMapping.Entries()) + { + if (_supportedTypes.ContainsKey(type.Value)) + { + if (_serializeDiscriminatorProperty) + { + throw new InvalidOperationException( + "Multiple discriminators on single type are not supported " + + "when discriminator serialization is enabled"); + } + } + else + { + _supportedTypes.Add(type.Value, type.Key); + } + } + } + + internal override NullableDictionary<object, Type> GetSubTypeMapping(Type type) + { + return _subTypeMapping; + } + + public override bool CanConvert(Type objectType) + { + return base.CanConvert(objectType) || _supportedTypes.ContainsKey(objectType); + } + + public override bool CanWrite + { + get + { + if (!_serializeDiscriminatorProperty) + return false; + + if (!_isInsideWrite) + return true; + + if (_allowNextWrite) + { + _allowNextWrite = false; + return true; + } + + _allowNextWrite = true; + return false; + } + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + JObject jsonObj; + _isInsideWrite = true; + _allowNextWrite = false; + try + { + jsonObj = JObject.FromObject(value, serializer); + } + finally + { + _isInsideWrite = false; + } + + if (!_supportedTypes.TryGetValue(value.GetType(), out var supportedType)) + { + throw new JsonSerializationException("Impossible to serialize type: " + value.GetType().FullName + " because there is no registered mapping for the discriminator property"); + } + var typeMappingPropertyValue = JToken.FromObject(supportedType, serializer); + if (_addDiscriminatorFirst) + { + jsonObj.AddFirst(new JProperty(JsonDiscriminatorPropertyName, typeMappingPropertyValue)); + } + else + { + jsonObj.Add(JsonDiscriminatorPropertyName, typeMappingPropertyValue); + } + jsonObj.WriteTo(writer); + } + } +} diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesByDiscriminatorValueConverter.cs.meta b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesByDiscriminatorValueConverter.cs.meta new file mode 100644 index 00000000..a9050e3c --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesByDiscriminatorValueConverter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a1452cb028e9c584cb53178f2ea70887 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesByPropertyPresenceConverter.cs b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesByPropertyPresenceConverter.cs new file mode 100644 index 00000000..8b89ac9e --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesByPropertyPresenceConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace JsonSubTypes +{ + internal class JsonSubtypesByPropertyPresenceConverter : JsonSubtypesConverter + { + private readonly Dictionary<string, Type> _jsonPropertyName2Type; + + internal JsonSubtypesByPropertyPresenceConverter(Type baseType, Dictionary<string, Type> jsonProperty2Type, Type fallbackType) : base(baseType, fallbackType) + { + _jsonPropertyName2Type = jsonProperty2Type; + } + + internal override Dictionary<string, Type> GetTypesByPropertyPresence(Type parentType) + { + return _jsonPropertyName2Type; + } + } +} diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesByPropertyPresenceConverter.cs.meta b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesByPropertyPresenceConverter.cs.meta new file mode 100644 index 00000000..571fa4b7 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesByPropertyPresenceConverter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d7fcded1d4a398143824a7e626cf1b19 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesConverter.cs b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesConverter.cs new file mode 100644 index 00000000..80f436f9 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesConverter.cs @@ -0,0 +1,54 @@ +using System; +using System.Reflection; + +namespace JsonSubTypes +{ + // MIT License + // + // Copyright (c) 2017 Emmanuel Counasse + // + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to deal + // in the Software without restriction, including without limitation the rights + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + // copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + // + // The above copyright notice and this permission notice shall be included in all + // copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + // SOFTWARE. + internal class JsonSubtypesConverter : JsonSubtypes + { + private readonly Type _baseType; + private readonly Type _fallbackType; + + public JsonSubtypesConverter(Type baseType, Type fallbackType) : base() + { + _baseType = baseType; + _fallbackType = fallbackType; + } + + public JsonSubtypesConverter(Type baseType, string jsonDiscriminatorPropertyName, Type fallbackType) : base(jsonDiscriminatorPropertyName) + { + _baseType = baseType; + _fallbackType = fallbackType; + } + + internal override Type GetFallbackSubType(Type type) + { + return _fallbackType; + } + + public override bool CanConvert(Type objectType) + { + return objectType == _baseType || ToTypeInfo(_baseType).IsAssignableFrom(ToTypeInfo(objectType)); + } + } +} diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesConverter.cs.meta b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesConverter.cs.meta new file mode 100644 index 00000000..53d15fb2 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesConverter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef63f12cd6c544246a99ee6371f64ff5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesConverterBuilder.cs b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesConverterBuilder.cs new file mode 100644 index 00000000..49c5c484 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesConverterBuilder.cs @@ -0,0 +1,91 @@ +using System; +using Newtonsoft.Json; + +namespace JsonSubTypes +{ + // MIT License + // + // Copyright (c) 2017 Emmanuel Counasse + // + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to deal + // in the Software without restriction, including without limitation the rights + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + // copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + // + // The above copyright notice and this permission notice shall be included in all + // copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + // SOFTWARE. + + public class JsonSubtypesConverterBuilder + { + private Type _baseType; + private string _discriminatorProperty; + private readonly NullableDictionary<object, Type> _subTypeMapping = new NullableDictionary<object, Type>(); + private bool _serializeDiscriminatorProperty; + private bool _addDiscriminatorFirst; + private Type _fallbackSubtype; + + public static JsonSubtypesConverterBuilder Of(Type baseType, string discriminatorProperty) + { + var customConverterBuilder = new JsonSubtypesConverterBuilder + { + _baseType = baseType, + _discriminatorProperty = discriminatorProperty + }; + return customConverterBuilder; + } + + public static JsonSubtypesConverterBuilder Of<T>(string discriminatorProperty) + { + return Of(typeof(T), discriminatorProperty); + } + + public JsonSubtypesConverterBuilder SerializeDiscriminatorProperty() + { + return SerializeDiscriminatorProperty(false); + } + + public JsonSubtypesConverterBuilder SerializeDiscriminatorProperty(bool addDiscriminatorFirst) + { + _serializeDiscriminatorProperty = true; + _addDiscriminatorFirst = addDiscriminatorFirst; + return this; + } + + public JsonSubtypesConverterBuilder RegisterSubtype(Type subtype, object value) + { + _subTypeMapping.Add(value, subtype); + return this; + } + + public JsonSubtypesConverterBuilder RegisterSubtype<T>(object value) + { + return RegisterSubtype(typeof(T), value); + } + + public JsonSubtypesConverterBuilder SetFallbackSubtype(Type fallbackSubtype) + { + _fallbackSubtype = fallbackSubtype; + return this; + } + + public JsonSubtypesConverterBuilder SetFallbackSubtype<T>(object value) + { + return RegisterSubtype(typeof(T), value); + } + + public JsonConverter Build() + { + return new JsonSubtypesByDiscriminatorValueConverter(_baseType, _discriminatorProperty, _subTypeMapping, _serializeDiscriminatorProperty, _addDiscriminatorFirst, _fallbackSubtype); + } + } +} diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesConverterBuilder.cs.meta b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesConverterBuilder.cs.meta new file mode 100644 index 00000000..e9c4ce70 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesConverterBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c0b2e769528947b4ea1d7135b59c1050 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesWithPropertyConverterBuilder.cs b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesWithPropertyConverterBuilder.cs new file mode 100644 index 00000000..72cbe21d --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesWithPropertyConverterBuilder.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace JsonSubTypes +{ + public class JsonSubtypesWithPropertyConverterBuilder + { + private readonly Type _baseType; + private readonly Dictionary<string, Type> _subTypeMapping = new Dictionary<string, Type>(); + private Type _fallbackSubtype; + + private JsonSubtypesWithPropertyConverterBuilder(Type baseType) + { + _baseType = baseType; + } + + public static JsonSubtypesWithPropertyConverterBuilder Of(Type baseType) + { + return new JsonSubtypesWithPropertyConverterBuilder(baseType); + } + + public static JsonSubtypesWithPropertyConverterBuilder Of<T>() + { + return Of(typeof(T)); + } + + public JsonSubtypesWithPropertyConverterBuilder RegisterSubtypeWithProperty(Type subtype, string jsonPropertyName) + { + _subTypeMapping.Add(jsonPropertyName, subtype); + return this; + } + + public JsonSubtypesWithPropertyConverterBuilder RegisterSubtypeWithProperty<T>(string jsonPropertyName) + { + return RegisterSubtypeWithProperty(typeof(T), jsonPropertyName); + } + + public JsonSubtypesWithPropertyConverterBuilder SetFallbackSubtype(Type fallbackSubtype) + { + _fallbackSubtype = fallbackSubtype; + return this; + } + + public JsonSubtypesWithPropertyConverterBuilder SetFallbackSubtype<T>() + { + return SetFallbackSubtype(typeof(T)); + } + + public JsonConverter Build() + { + return new JsonSubtypesByPropertyPresenceConverter(_baseType, _subTypeMapping, _fallbackSubtype); + } + } +} diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesWithPropertyConverterBuilder.cs.meta b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesWithPropertyConverterBuilder.cs.meta new file mode 100644 index 00000000..b2376f7c --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/JsonSubtypesWithPropertyConverterBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2898a10da3eecb7488d8007210c2349f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/NullableDictionary.cs b/Assets/Plugins/JsonSubTypes/JsonSubTypes/NullableDictionary.cs new file mode 100644 index 00000000..491fe32e --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/NullableDictionary.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; + +namespace JsonSubTypes +{ + public class NullableDictionary<TKey, TValue> + { + private bool _hasNullKey; + private TValue _nullKeyValue; + private readonly Dictionary<TKey, TValue> _dictionary = new Dictionary<TKey, TValue>(); + + public bool TryGetValue(TKey key, out TValue value) + { + if (Equals(key, default(TKey))) + { + if (!_hasNullKey) + { + value = default(TValue); + return false; + } + + value = _nullKeyValue; + return true; + } + + return _dictionary.TryGetValue(key, out value); + } + + public void Add(TKey key, TValue value) + { + if (Equals(key, default(TKey))) + { + if (_hasNullKey) + { + throw new ArgumentException(); + } + + _hasNullKey = true; + _nullKeyValue = value; + } + else + { + _dictionary.Add(key, value); + } + } + + public IEnumerable<TKey> NotNullKeys() + { + return _dictionary.Keys; + } + + public IEnumerable<KeyValuePair<TKey, TValue>> Entries() + { + if (_hasNullKey) + { + yield return new KeyValuePair<TKey, TValue>(default(TKey), _nullKeyValue); + } + + foreach (var value in _dictionary) + { + yield return value; + } + } + } +} diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/NullableDictionary.cs.meta b/Assets/Plugins/JsonSubTypes/JsonSubTypes/NullableDictionary.cs.meta new file mode 100644 index 00000000..1f5dcdbc --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/NullableDictionary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 44161e4db554878499ec521f2cb410af +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/packages.config b/Assets/Plugins/JsonSubTypes/JsonSubTypes/packages.config new file mode 100644 index 00000000..851e05d3 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/packages.config @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="Newtonsoft.Json" version="12.0.3" targetFramework="net47" allowedVersions="[10.0.0, 13.0.0)" /> +</packages> diff --git a/Assets/Plugins/JsonSubTypes/JsonSubTypes/packages.config.meta b/Assets/Plugins/JsonSubTypes/JsonSubTypes/packages.config.meta new file mode 100644 index 00000000..ac1e1b46 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/JsonSubTypes/packages.config.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: d16d099b804bc914982bbbd303b30755 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/JsonSubTypes/LICENSE b/Assets/Plugins/JsonSubTypes/LICENSE new file mode 100644 index 00000000..3a790d50 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Emmanuel Counasse + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Assets/Plugins/JsonSubTypes/LICENSE.meta b/Assets/Plugins/JsonSubTypes/LICENSE.meta new file mode 100644 index 00000000..df72ee82 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/LICENSE.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1a118c93a66a64e41ac1b53833cd5d3c +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/JsonSubTypes/README.md b/Assets/Plugins/JsonSubTypes/README.md new file mode 100644 index 00000000..0b17b2d9 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/README.md @@ -0,0 +1,234 @@ +# __JsonSubTypes__ +__JsonSubTypes__ is a discriminated Json sub-type Converter implementation for .NET + +[](https://ci.appveyor.com/project/manuc66/jsonsubtypes/branch/master) +[](https://codecov.io/gh/manuc66/JsonSubTypes) +[](https://sonarcloud.io/dashboard?id=manuc66%3AJsonSubtypes) +[](https://www.nuget.org/packages/JsonSubTypes/) +[](https://www.nuget.org/packages/JsonSubTypes) +[](https://www.codefactor.io/repository/github/manuc66/JsonSubTypes) +[](https://app.fossa.io/projects/git%2Bgithub.com%2Fmanuc66%2FJsonSubTypes?ref=badge_shield) + +## DeserializeObject with custom type property name + +```csharp +[JsonConverter(typeof(JsonSubtypes), "Kind")] +public interface IAnimal +{ + string Kind { get; } +} + +public class Dog : IAnimal +{ + public string Kind { get; } = "Dog"; + public string Breed { get; set; } +} + +public class Cat : IAnimal { + public string Kind { get; } = "Cat"; + public bool Declawed { get; set;} +} +``` + +The second parameter of the `JsonConverter` attribute is the JSON property name that will be use to retreive the type information from JSON. + +```csharp +var animal = JsonConvert.DeserializeObject<IAnimal>("{\"Kind\":\"Dog\",\"Breed\":\"Jack Russell Terrier\"}"); +Assert.AreEqual("Jack Russell Terrier", (animal as Dog)?.Breed); +``` + +N.B.: This only works for types in the same assembly as the base type/interface and either in the same namespace or with a fully qualified type name. + +## DeserializeObject with custom type mapping + +```csharp +[JsonConverter(typeof(JsonSubtypes), "Sound")] +[JsonSubtypes.KnownSubType(typeof(Dog), "Bark")] +[JsonSubtypes.KnownSubType(typeof(Cat), "Meow")] +public class Animal +{ + public virtual string Sound { get; } + public string Color { get; set; } +} + +public class Dog : Animal +{ + public override string Sound { get; } = "Bark"; + public string Breed { get; set; } +} + +public class Cat : Animal +{ + public override string Sound { get; } = "Meow"; + public bool Declawed { get; set; } +} +``` + +```csharp +var animal = JsonConvert.DeserializeObject<IAnimal>("{\"Sound\":\"Bark\",\"Breed\":\"Jack Russell Terrier\"}"); +Assert.AreEqual("Jack Russell Terrier", (animal as Dog)?.Breed); +``` + +N.B.: Also works with other kind of value than string, i.e.: enums, int, ... + +## SerializeObject and DeserializeObject with custom type property only present in JSON + +This mode of operation only works when JsonSubTypes is explicitely registered in JSON.NET's serializer settings, and not through the ``[JsonConverter]`` attribute. + +```csharp +public abstract class Animal +{ + public int Age { get; set; } +} + +public class Dog : Animal +{ + public bool CanBark { get; set; } = true; +} + +public class Cat : Animal +{ + public int Lives { get; set; } = 7; +} + +public enum AnimalType +{ + Dog = 1, + Cat = 2 +} +``` + +### Registration: + +```csharp +var settings = new JsonSerializerSettings(); +settings.Converters.Add(JsonSubtypesConverterBuilder + .Of(typeof(Animal), "Type") // type property is only defined here + .RegisterSubtype(typeof(Cat), AnimalType.Cat) + .RegisterSubtype(typeof(Dog), AnimalType.Dog) + .SerializeDiscriminatorProperty() // ask to serialize the type property + .Build()); +``` + +or using syntax with generics: + +```csharp +var settings = new JsonSerializerSettings(); +settings.Converters.Add(JsonSubtypesConverterBuilder + .Of<Animal>("Type") // type property is only defined here + .RegisterSubtype<Cat>(AnimalType.Cat) + .RegisterSubtype<Dog>(AnimalType.Dog) + .SerializeDiscriminatorProperty() // ask to serialize the type property + .Build()); +``` + +### De-/Serialization: +```csharp +var cat = new Cat { Age = 11, Lives = 6 } + +var json = JsonConvert.SerializeObject(cat, settings); + +Assert.Equal("{\"Lives\":6,\"Age\":11,\"Type\":2}", json); + +var result = JsonConvert.DeserializeObject<Animal>(json, settings); + +Assert.Equal(typeof(Cat), result.GetType()); +Assert.Equal(11, result.Age); +Assert.Equal(6, (result as Cat)?.Lives); +``` + +## DeserializeObject mapping by property presence + +```csharp +[JsonConverter(typeof(JsonSubtypes))] +[JsonSubtypes.KnownSubTypeWithProperty(typeof(Employee), "JobTitle")] +[JsonSubtypes.KnownSubTypeWithProperty(typeof(Artist), "Skill")] +public class Person +{ + public string FirstName { get; set; } + public string LastName { get; set; } +} + +public class Employee : Person +{ + public string Department { get; set; } + public string JobTitle { get; set; } +} + +public class Artist : Person +{ + public string Skill { get; set; } +} +``` + +or using syntax with generics: + + +```csharp +string json = "[{\"Department\":\"Department1\",\"JobTitle\":\"JobTitle1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}," + + "{\"Department\":\"Department1\",\"JobTitle\":\"JobTitle1\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}," + + "{\"Skill\":\"Painter\",\"FirstName\":\"FirstName1\",\"LastName\":\"LastName1\"}]"; + + +var persons = JsonConvert.DeserializeObject<IReadOnlyCollection<Person>>(json); +Assert.AreEqual("Painter", (persons.Last() as Artist)?.Skill); +``` + + +### Registration: +```cs +settings.Converters.Add(JsonSubtypesWithPropertyConverterBuilder + .Of(typeof(Person)) + .RegisterSubtypeWithProperty(typeof(Employee), "JobTitle") + .RegisterSubtypeWithProperty(typeof(Artist), "Skill") + .Build()); +``` + +or + +```cs +settings.Converters.Add(JsonSubtypesWithPropertyConverterBuilder + .Of<Person>() + .RegisterSubtypeWithProperty<Employee>("JobTitle") + .RegisterSubtypeWithProperty<Artist>("Skill") + .Build()); +``` + + +## A default class other than the base type can be defined + +```cs +[JsonConverter(typeof(JsonSubtypes))] +[JsonSubtypes.KnownSubType(typeof(ConstantExpression), "Constant")] +[JsonSubtypes.FallBackSubType(typeof(UnknownExpression))] +public interface IExpression +{ + string Type { get; } +} +``` + +Or with code configuration: +```cs +settings.Converters.Add(JsonSubtypesConverterBuilder + .Of(typeof(IExpression), "Type") + .SetFallbackSubtype(typeof(UnknownExpression)) + .RegisterSubtype(typeof(ConstantExpression), "Constant") + .Build()); +``` +```cs +settings.Converters.Add(JsonSubtypesWithPropertyConverterBuilder + .Of(typeof(IExpression)) + .SetFallbackSubtype(typeof(UnknownExpression)) + .RegisterSubtype(typeof(ConstantExpression), "Value") + .Build()); +``` + +## 💖 Support this project +If this project helped you save money or time or simply makes your life also easier, you can give me a cup of coffee =) + +- [](https://www.paypal.me/manuc66) +- Bitcoin — You can send me bitcoins at this address: `33gxVjey6g4Beha26fSQZLFfWWndT1oY3F` + + +## License +[](https://app.fossa.io/projects/git%2Bgithub.com%2Fmanuc66%2FJsonSubTypes?ref=badge_large) \ No newline at end of file diff --git a/Assets/Plugins/JsonSubTypes/README.md.meta b/Assets/Plugins/JsonSubTypes/README.md.meta new file mode 100644 index 00000000..a4bdd86e --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ca5ac66561eb3414baa24690951449ad +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/JsonSubTypes/_config.yml b/Assets/Plugins/JsonSubTypes/_config.yml new file mode 100644 index 00000000..b8497135 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-leap-day \ No newline at end of file diff --git a/Assets/Plugins/JsonSubTypes/_config.yml.meta b/Assets/Plugins/JsonSubTypes/_config.yml.meta new file mode 100644 index 00000000..1064e947 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/_config.yml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: fbec9b4469d2fab4b9aab435aeb78e29 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/JsonSubTypes/appveyor.yml b/Assets/Plugins/JsonSubTypes/appveyor.yml new file mode 100644 index 00000000..388417bb --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/appveyor.yml @@ -0,0 +1,57 @@ +image: Visual Studio 2017 + +environment: + sonar_token: + secure: 4M8z89LHqy9Rho8Mi4i7V0Ajz/7kvDYolVFH2V0GjK5VO5dseuv2Dja/qmKbio1I + github_auth_token: + secure: XUib5rF8Uxwk7S2umtsFEQ9t5G0p/J3G3PNpWLZuNIyw/zNufLDCge8C6QJRTzQw + +init: + - choco install opencover.portable + - choco install codecov + - choco install "sonarscanner-msbuild-net46" -y + +before_build: + - nuget restore + - ps: >- + if ($env:APPVEYOR_PULL_REQUEST_NUMBER) { + SonarScanner.MSBuild.exe begin /k:"manuc66:JsonSubtypes" /d:"sonar.host.url=https://sonarcloud.io" /o:"manuc66-github" /d:"sonar.login=$env:sonar_token" /d:sonar.cs.opencover.reportsPaths=".\MyProject_coverage.xml" /d:sonar.cs.nunit.reportsPaths="nunitTestResult.xml" /d:"sonar.cpd.exclusions=**/*Tests.cs" /d:"sonar.language=cs" /d:"sonar.pullrequest.base=master" /d:"sonar.pullrequest.branch=$env:APPVEYOR_REPO_BRANCH" /d:"sonar.pullrequest.key=$env:APPVEYOR_PULL_REQUEST_NUMBER" /d:"sonar.pullrequest.provider=GitHub" /d:"sonar.pullrequest.github.repository=$env:APPVEYOR_REPO_NAME" /d:"sonar.github.oauth=$env:github_auth_token" + } + elseif ($env:APPVEYOR_REPO_BRANCH -eq "master") { + SonarScanner.MSBuild.exe begin /k:"manuc66:JsonSubtypes" /d:"sonar.host.url=https://sonarcloud.io" /o:"manuc66-github" /d:"sonar.login=$env:sonar_token" /d:sonar.cs.opencover.reportsPaths=".\MyProject_coverage.xml" /d:sonar.cs.nunit.reportsPaths="nunitTestResult.xml" /d:"sonar.cpd.exclusions=**/*Tests.cs" /d:"sonar.language=cs" + } + else { + SonarScanner.MSBuild.exe begin /k:"manuc66:JsonSubtypes" /d:"sonar.host.url=https://sonarcloud.io" /o:"manuc66-github" /d:"sonar.login=$env:sonar_token" /d:"sonar.branch.name=$env:APPVEYOR_REPO_BRANCH" /d:"sonar.branch.target=master" /d:sonar.cs.opencover.reportsPaths=".\MyProject_coverage.xml" /d:sonar.cs.nunit.reportsPaths="nunitTestResult.xml" /d:"sonar.cpd.exclusions=**/*Tests.cs" /d:"sonar.language=cs" + } + +configuration: Release + +nuget: + disable_publish_on_pr: true + +build: + project: JsonSubTypes.sln + verbosity: minimal + +test_script: + - dir /S JsonSubTypes\bin\Release + - dir /S JsonSubTypes.Tests\bin\Release + - OpenCover.Console.exe -register:admin -target:"nunit3-console.exe" -targetargs:".\JsonSubTypes.Tests\bin\Release\net46\JsonSubTypes.Tests.dll --result:nunitTestResult.xml" -filter:"+[JsonSubTypes*]* -[JsonSubTypes.Tests]*" -output:".\MyProject_coverage.xml" + - codecov -f "MyProject_coverage.xml" + +after_test: + - ps: SonarScanner.MSBuild.exe end /d:"sonar.login=$env:sonar_token" + +artifacts: + # pushing all *.nupkg files in build directory recursively + - path: 'JsonSubTypes\**\*.nupkg' + +deploy: + - provider: NuGet + name: NuGet + server: https://www.nuget.org/api/v2/package + api_key: + secure: I8mGPBaVaAK9e3mBzOBoqQLdPckvVYvSJqFc2BE7/P7UF3P3OqHkxUAa1/PAARuy + artifact: /JsonSubTypes.*\.nupkg/ + on: + branch: master diff --git a/Assets/Plugins/JsonSubTypes/appveyor.yml.meta b/Assets/Plugins/JsonSubTypes/appveyor.yml.meta new file mode 100644 index 00000000..3e79bba9 --- /dev/null +++ b/Assets/Plugins/JsonSubTypes/appveyor.yml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2ac5533b3a05efc48b47585a01cec60b +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: -- GitLab