When using a specific .ctor via JsonConstructor for deserializing IList<ISomeInterface> properties, the parameter names must match the original Json names and the JsonProperty mapping on those properties are not used.
Example:
SpokenLanguages parameter is always null since it does not match spoken_languages, but there is a JsonProperty mapping it:
public partial class AClass : ISomeBase
{
public AClass() { }
[JsonConstructor]
public AClass(IList<SysType> SysTypes, IList<ProductionCountry> production_countries, IList<SpokenLanguage> SpokenLanguages)
{
this.Genres = SysTypes?.ToList<IGenre>();
this.ProductionCountries = production_countries?.ToList<IProductionCountry>();
this.SpokenLanguages = SpokenLanguages?.ToList<ISpokenLanguage>();
}
public int Id { get; set; }
public IList<IGenre> Genres { get; set; }
[JsonProperty("production_countries")]
public IList<IProductionCountry> ProductionCountries { get; set; }
[JsonProperty("spoken_languages")]
public IList<ISpokenLanguage> SpokenLanguages { get; set; }
}
Is this just a "limitation" of how Json.Net calls the constructor or is there something I am missing.
FYI: I am code generating all this via Rosyln and am not looking at generating a JsonConverter for each type for this...
When Json.NET invokes a parameterized constructor, it matches JSON properties to constructor arguments by name, using an ordinal case-ignoring match. However, for JSON properties that also correspond to type members, which name does it use - the member name, or the override type member name specified by JsonPropertyAttribute.PropertyName?
It appears you are hoping it matches on both, since your argument naming conventions are inconsistent:
The constructor argument production_countries matches the overridden property name:
[JsonProperty("production_countries")]
public IList<IProductionCountry> ProductionCountries { get; set; }
The constructor argument IList<SpokenLanguage> SpokenLanguages matches the reflected name rather than the overridden property name:
[JsonProperty("spoken_languages")]
public IList<ISpokenLanguage> SpokenLanguages { get; set; }
IList<SysType> SysTypes matches neither (is this a typo in the question?)
However, what matters is the property name in the JSON file itself and the constructor argument name as shown in JsonSerializerInternalReader.ResolvePropertyAndCreatorValues(). A simplified version of the algorithm is as follows:
The property name is read from the JSON file.
A closest match constructor argument is found (if any).
A closest match member name is found (if any).
If the JSON property matched a constructor argument, deserialize to that type and pass into the constructor,
But if not, deserialize to the appropriate member type and set the member value after construction.
(The implementation becomes complex when a JSON property matches both and developers expect that, for instance, [JsonProperty(Required = Required.Always)] added to the member should be respected when set in the constructor.)
Thus the constructor argument production_countries will match a value named "production_countries" in the JSON, while the constructor argument SpokenLanguages will not match a JSON value named "spoken_languages".
So, how to deserialize your type successfully? Firstly, you could mark the constructor parameters with [JsonProperty(overrideName)] to override the constructor name used during deserialization:
public partial class AClass : ISomeBase
{
public AClass() { }
[JsonConstructor]
public AClass([JsonProperty("Genres")] IList<SysType> SysTypes, IList<ProductionCountry> production_countries, [JsonProperty("spoken_languages")] IList<SpokenLanguage> SpokenLanguages)
{
this.Genres = SysTypes == null ? null : SysTypes.Cast<IGenre>().ToList();
this.ProductionCountries = production_countries == null ? null : production_countries.Cast<IProductionCountry>().ToList();
this.SpokenLanguages = SpokenLanguages == null ? null : SpokenLanguages.Cast<ISpokenLanguage>().ToList();
}
Secondly, since you seem to be using the constructor to deserialize items in collections containing interfaces as concrete objects, you could consider using a single generic converter based on CustomCreationConverter as an ItemConverter:
public partial class AClass : ISomeBase
{
public AClass() { }
public int Id { get; set; }
[JsonProperty(ItemConverterType = typeof(CustomCreationConverter<IGenre, SysType>))]
public IList<IGenre> Genres { get; set; }
[JsonProperty("production_countries", ItemConverterType = typeof(CustomCreationConverter<IProductionCountry, ProductionCountry>))]
public IList<IProductionCountry> ProductionCountries { get; set; }
[JsonProperty("spoken_languages", ItemConverterType = typeof(CustomCreationConverter<ISpokenLanguage, SpokenLanguage>))]
public IList<ISpokenLanguage> SpokenLanguages { get; set; }
}
public class CustomCreationConverter<T, TSerialized> : CustomCreationConverter<T> where TSerialized : T, new()
{
public override T Create(Type objectType)
{
return new TSerialized();
}
}
Example fiddle showing both options.
Related
I have a class Type with string property Name and T property value. I am receiving a Json object with different properties, two of them are name and value.
I am creating with reflection the Type (in the example I created explicity a boolean) and I need to assign the received String value to the Type.Value property that can be any type.
How can I do that ? The type can be int string, List (any known type) or a new type that I have created. I don't want to switch over the received string type name to create a specific value type.
I want a generic way to do it to avoid updating this method every time that I create a new type in my system. If I create the Type class instance with reflection, I want also to update the value property on runtime without knowing the type.
My code doesn't handle the deserialization (this is taken place in other code that I don't have access to. Even with acess, the project where the deserialize is taken place, doesn't recognize the custom types because it is a common/util project. So in this case, I need to focus only on converting a string value to a T value.
public class Type<T>
{
public string Name { get; set; }
public Type Type => typeof(T)
public T Value { get; set; }
}
public MyResultObject
{
public string ReqId { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
var jsonResult = myResultObject //type of MyResultObject
var type = new type<bool>(); //in my code this is created with reflection
type.Name = jsonResult.Name;
type.Value = jsonResult.Value as bool ??? //I want to convert the string Value to the explicit type that can be any T type
The best approach depends on details you haven't included.
If you don't know the type at the point where you're deserializing, but you do know the type at some later point in your code, one solution could be to leave the Value property as a JToken, and then convert it when your code knows what type it needs:
public MyResultObject
{
public string ReqId { get; set; }
public string Name { get; set; }
public JToken Value { get; set; }
}
type.Value = jsonResult.Value<bool>(); // or .Value<T>()
If you need to deserialize the object to the right concrete type but you don't know at compile-time what that type might be, JSON.NET has a built-in feature to handle this: TypeNameHandling. If you serialize and deserialize your objects using serializer options like this:
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
}
... then you can make your Value property an object and JSON.NET will automatically add type metadata into the serialized object so that it can be serialized to the right type.
There are two potential down-sides to that approach, though. One is that you have to be in control of both serialization and deserialization. The other is that there may be security implications if someone you don't trust is providing the JSON: they could instantiate some random object type that you don't expect them to be able to create.
There's a middle-ground approach where you use a custom type converter to determine which type of object to create based on the value of something else on the JSON object, but you have more control over which types of objects might be created, and how that gets represented in the JSON.
Your sample doesn't demonstrate the issue well enough. If you know <T> of Type<T> just add a method on your Type<T> class that does the parsing via JsonConvert from NewtonsoftJson or JsonSerializer from System.Text.Json and problem will be solved. Am I missing something here?
public class Type<T>
{
public string Name { get; set; }
public Type GenericType => typeof(T);
public T Value { get; set; }
public void SetValueFromString(string value)
{
Value = JsonConvert.DeserializeObject<T>(value);
}
}
In my application I have a custom attribute calles ResourceTargetAttribute which looks like:
[AttributeUsage(AttributeTargets.Property)]
private class ResourceTargetAttribute : Attribute
{
public ResourceTargetAttribute(string resourceKey)
{
ResourceKey = resourceKey;
}
public string ResourceKey { get; private set; }
}
The usage looks like:
[ResourceTarget("FileNotFoundErrorText")
public string FileNotFoundErrorText { get; private set; }
The constructor of the class where the FileNotFoundErrorText-Property is defined resolves this attribute. This just works fine.
Now I was thinking about to extend the attribute to have a parameterless constructor and if this is called the name of the Property the attribute is on will automatically be used for the ResourceKey.
Therefore I've introduced a new constructor which just looks like:
public ResourceTargetAttribute()
{
}
And the usage then should look like:
[ResourceTarget()]
public string FileNotFoundErrorText { get; private set; }
And here I want to have name of the FileNotFoundErrorText-Property automatically be passed to the ResourceTarget-Attribute.
Is there a possibility to do this?
The CallerMemberNameAttribute might help you:
public ResourceTargetAttribute([CallerMemberName] string propertyName = null)
{
ResourceKey = propertyName;
}
Usage:
[ResourceTarget]
public string FileNotFoundErrorText { get; private set; }
If you get the attribute, the
attr.ResourceKey
property should contain FileNotFoundErrorText as value.
Otherwise I just would go the way passing the name as string as attributes are metadata applied to the members of a type, the type itself, method parameters or the assembly so you must have the original member itself to access its meta data.
The easiest way would be to utilize nameof-operator:
[ResourceTarget(nameof(FileNotFoundErrorText)]
public string FileNotFoundErrorText { get; private set; }
Another approach would be to modify the code that actual examines / searches for these marker-attributes. use reflection to get the actual Property-Name on which the attribute was applied.
Maybe if you provide the mentioned "constructor-code" I could further assist.
I'm trying to create a collection (list<> or IEnumerable<>) of a custom objet "InventorAttribue" that has 2 properties; Name and Value.
The "Value" property can be of various type so I thought of coding this object like this:
public class InventorAttribute<T> {
public InventorAttribute (string name, T value) {
Name = name;
Value = value;
}
public string Name { get; set; }
public T Value { get; set; }
}
Further I plan to use an "AttiributeSet" class to represent the final Autodesk Inventor AttributeSet to be stored in an Inventor's object. Here is the class and where my question stands, because of course, this code does not work as the type 'T' cannot be found (!)
public class AttributeSet
{
public AttributeSet(string category, string name {
Name = name;
Attributes = new List<InventorAttribute<T>>();
}
public string Category { get; set; }
public string Name { get; set; }
public List<InventorAttribute<T>> Attributes { get; set; }
public void AddAttribute(string name, T value){
Attributes.Add(new InventorAttribute<T>(name,value));
}
}
Question:
How can I manage to write this code, and being able to pass the "InventorAttribute.Value" type only at run time through the "AddAttribute" method.
Thanks in advance for greatly appreciated help.
Your AttributeSet class should be also parametrized:
public class AttributeSet<T>
NOTE: you cannot store InventorAttribute<T> parametrized with different T types in Attributes collection. Even if you could do that, how would you consume such collection? You will need to cast Value for each attribute to appropriate type. You will not have any benefits of having generic class here. So create non-generic InventorAttribute which will store values in property of object type.
You're probably imagining some form of inheritance. It doesn't exist here.
An InventorAttribute<string> is not a subclass of InventorAttribute<T>. Nor is it a subclass of InventorAttribute<object> (I mention this since it's usually people's next attempt to define the collection's item type). Each constructed generic type is effectively independent1.
If applicable, you may be able to introduce a new base class:
public abstract class InventorAttribute {
public string Name { get; set; }
public InventorAttribute (string name) {
Name = name;
}
}
public class InventorAttribute<T> : InventorAttribute {
public InventorAttribute (string name, T value) : base(name) {
Value = value;
}
public T Value { get; set; }
}
And you can now declare your collection to be of non-generic type InventorAttribute. But now you cannot access the Values until you cast to the more specific type.
1So far as the type system is concerned. As an implementation detail, the system is able to cleverly JIT only a single version of each method body that is applicable for all reference types. But that doesn't have any visible impact in the type system.
I want to create a key value table in my database along the lines of
public class KeyValue {
public string Id { get; set; }
public dynamic Value {get; set; }
}
Using a slightly modified SqlProvider I have no problems getting CreateTable<KeyValue>() to generate varchar(1024) Id, varchar(max) Value.
I have no issues saving objects to it. The problem is when I load the objects
var content = dbConn.GetById<KeyValue>("about");
content.Value at this point is a string.
Looking at the database record, the text for value does not appear to store any type information.
Is there really anything I can do better other than manually invoking ServiceStack.Text and call deserialize with the appropriate type information?
I do not need absolute dynamic, my actual use case is for polymorphism with a base class instead of dynamic. So I don't really care what type Value is whether it's the base class, dynamic, object, etc. Regardless other than using the class
public class KeyValue {
public string Id { get; set; }
public MySpecificChildType Value {get; set; }
}
I haven't been able to get anything other than a string back for Value. Can I tell OrmLite to serialize the type information to be able to correctly deserialize my objects or do I just have to do it manually?
Edit: some further information. OrmLite is using the Jsv serializer defined by ServiceStack.Text.TypeSerializer and is in no way pluggable in the BSD version. If I add a Type property to my KeyValue class with the dynamic Value I can do
var value = content.Value as string;
MySpecificChildType strongType =
TypeSerializer.DeserializeFromString(content, content.Type);
I just really want a better way to do this, I really don't like an object of 1 type going into the db coming back out with a different type (string).
I haven't worked much with the JsvSerializer but with the JsonSerializer you can achieve this (in a few different ways) and as of ServiceStack 4.0.11 you can opt to use the JsonSerializer instead, see https://github.com/ServiceStack/ServiceStack/blob/master/release-notes.md#v4011-release-notes.
Example
public abstract class BaseClass {
//Used for second example of custom type lookup
public abstract string Type { get; set; }
}
public class ChildA : BaseClass {
//Used for second example of custom type lookup
public override string Type { get; set; }
public string PropA { get; set; }
}
And then in your init/bootstrap class you can configure the serializer to emit the type information needed for proper deserialization:
public class Bootstrapper {
public void Init() {
ServiceStack.Text.JsConfig.ExcludeTypeInfo = false;
ServiceStack.Text.JsConfig.IncludeTypeInfo = true;
}
}
If you wish to use something other that the default "__type" attribute that ServiceStack uses (if you for example want to have a friendly name identifying the type rather then namespace/assembly) you can also configure your own custom type lookup as such
public class Bootstrapper {
public void Init() {
ServiceStack.Text.JsConfig.ExcludeTypeInfo = false;
ServiceStack.Text.JsConfig.IncludeTypeInfo = true;
ServiceStack.Text.JsConfig.TypeAttr = "type";
ServiceStack.Text.JsConfig.TypeFinder = type =>
{
if ("CustomTypeName".Equals(type, StringComparison.OrdinalIgnoreCase))
{
return typeof(ChildA);
}
return typeof(BaseClass);
}
}
}
I'd like to serialize a class to XML, assigning an XML attribute to it. Snippet:
[XmlType(TypeName = "classmy")]
public class MyClass2 : List<object>
{
[XmlAttribute(AttributeName = "myattr")]
public string Name { get; set; }
}
public class MyConst
{
public MyConst()
{
MyClass2 myClass2 = new MyClass2 { 10, "abc" };
myClass2.Name = "nomm";
XmlSerializer serializer = new XmlSerializer(typeof(MyClass2));
serializer.Serialize(Console.Out, myClass2);
}
}
But the resulting XML looks like this
<?xml version="1.0" encoding="IBM437"?>
<classmy xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<anyType xsi:type="xsd:int">10</anyType>
<anyType xsi:type="xsd:string">abc</anyType>
</classmy>
All well and good, with the only exception being that myClass2.Name is not serialized. I was expecting something in the line of
<classmy myattr="nomm" [...]>[...]</classmy>
... Why isn't that serialized, and how can it be?
dont derive List<T>, create class with member List
[XmlType(TypeName = "classmy")]
public class MyClass2
{
[XmlAttribute(AttributeName = "Items")]
List<object> Items { get; set; } //need to change type in `<>`
[XmlAttribute(AttributeName = "myattr")]
public string Name { get; set; }
}
Alternative solution: use an array instead of a list and XmlElement
[XmlType(TypeName = "classmy")]
public class MyClass2
{
[XmlElement(ElementName = "Items")]
public object[] Items { get; set; }
[XmlAttribute(AttributeName = "myattr")]
public string Name { get; set; }
}
XmlSerializer treats List<> in special way:
XmlSerializer can process classes that implement IEnumerable or ICollection differently if they meet certain requirements. A class that implements IEnumerable must implement a public Add method that takes a single parameter. The Add method's parameter must be consistent (polymorphic) with the type returned from the IEnumerator.Current property returned from the GetEnumerator method. A class that implements ICollection in addition to IEnumerable (such as CollectionBase) must have a public Item indexed property (an indexer in C#) that takes an integer, and it must have a public Count property of type integer. The parameter passed to the Add method must be the same type as that returned from the Item property, or one of that type's bases. For classes implementing ICollection, values to be serialized will be retrieved from the indexed Item property rather than by calling GetEnumerator. Also note that public fields and properties will not be serialized, with the exception of public fields that return another collection class (one that implements ICollection). MSDN - scroll to XML Serialization Considerations
That why it serialized Your class as a list of objects only, without Your property. The best solution is to include List as class public property and mark it as XmlElement.