3

Using Azure Durable Functions, I am trying to use the context.GetInput<model>() function which returns the specified model. The model being used has a parameter that is another model which is a derived class. The model that is outputted from context.GetInput<model>() returns the model with the base class instead of the derived class.

I have checked the $type specified in the context, which shows the derived class, but when checking the outputted model, the result is the base class.

for example:

public class Student{
   public Book book {get;set;}
}
public class Textbook:Book {
   public string classfor {get;set;}
}
public class Book {
   public string title {get;set;}
}

[ActivityTrigger] DurableActivityContextBase context is a parameter to the function.

Then I would be calling :

var model = context.GetInput<Student>()

where the context includes

{
  "$type": "Student",
    "book" : {
       "$type": "Textbook",
       "classfor" : "Math",
       "title" : "PreAlgebra"
    }
}

Yet the result is Model of student which contains a Book instead of Textbook, where the title is assigned "PreAlgebra"

I expect the output of Student model to have a Textbook with properties:

title = "PreAlgebra"
classfor = "Math"

but the actual Student output contains a Book with the property

title = "PreAlgebra"
mlam
  • 31
  • 4
  • Hi mlam, may I know if the answer below helps your question ? If it is helpful, could you please [mark](https://stackoverflow.com/help/someone-answers) my answer as "accepted" ? Thanks in advance~ – Hury Shen Nov 10 '19 at 02:10

3 Answers3

4

I've encountered the same problem you did last week. Unfortunately right now Azure Functions (even 2.x) don't support polymorphism for durable functions. The durable context serializes your object to JSON, but there's no way to pass JSON serialization settings as described here on GitHub. There's also another issue about this specific problem.

In my case I have an abstract base class, but you can use the same approach for your derived types. You can create a custom JSON converter that will deal with picking the correct type during deserialization. So for example if you have this sort of inheritance:

[JsonConverter(typeof(DerivedTypeConverter))]
public abstract class Base
{ 
    [JsonProperty("$type")]
    public abstract string Type { get; }
}

public class Child : Base
{
    public override string Type => nameof(Child);
}

public class Child2 : Base
{
    public override string Type => nameof(Child2);
}

Then you can have your а JSON Converter:

public class BaseDerivedTypeConverter : DefaultContractResolver
{
    // You need this to protect yourself against circular dependencies
    protected override JsonConverter ResolveContractConverter(Type objectType)
    {
        return typeof(Base).IsAssignableFrom(objectType) && !objectType.IsAbstract
            ? null
            : base.ResolveContractConverter(objectType);
    }
}

public class DerivedTypeConverter : JsonConverter
{
    private static readonly JsonSerializerSettings Settings =
        new JsonSerializerSettings()
        {
            ContractResolver = new BaseDerivedTypeConverter()
        };

    public override bool CanConvert(Type objectType) => (objectType == typeof(Base));

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jsonObject = JObject.Load(reader);

        // Make checks if jsonObject["$type"].Value<string>() has a supported type
        // You can have a static dictionary or a const array of supported types

        // You can leverage the array or dictionary to get the type you want again
        var type = Type.GetType("Full namespace to the type you want", false); // the false flag means that the method call won't throw an exception on error

        if (type != null)
        {
            return JsonConvert.DeserializeObject(jsonObject.ToString(), type, Settings);
        }
        else
        {
            throw new ValidationException("No valid $type has been specified!");
        }
    }

    public override bool CanWrite => false;

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}

In my usage when I call context.GetInput<Base>() I can get either Child or Child1 because Base is abstract.

In your case it can be Book or Student depending on what's the actual value. This also applies for other durable function operations like like

var foobar = await context.CallActivityAsync<Base>("FuncName", context.GetInput<int>());

The converter will deal with that and you'll get the object you want inside foobar.

underscoreHao
  • 331
  • 2
  • 9
  • The [link](https://github.com/Azure/azure-functions-durable-extension/issues/65) you shared shows that the json serialization can now be customized! Thanks for your help :) – mlam Dec 20 '19 at 00:30
0

Per my understanding, the class Textbook extends Book, so "Book" is parent class and "Textbook" is subclass. In your context, you want to turn the child class(Textbook) to the parent class(Book). After that, "book" will just have the attribute "title" which is their common attribute but doesn't have the specific attribute "classfor". You can refer to the code below:

enter image description here

Hury Shen
  • 14,948
  • 1
  • 9
  • 18
  • No, my expected should have the derived class's properties, but when executing context.GetInput(), the outputted model returns a model of Student with the parent class Book instead of the expected, which would be Textbook. – mlam Nov 11 '19 at 03:05
  • Hi @mlam, in my answer context I use c# code to explain to you why it can't return a model of Student with the child class(Textbook). The reason is when you cast the child class to the parent class, it will not show error but it can just have the fields from the parent class. So it can not return the result you expected from your class structure. You need to improve your class structure according to your requirements(for example: just use a "Student" class with "book" and a "Book" class with "title" and "classfor"), it depends on your needs. – Hury Shen Nov 11 '19 at 05:57
  • Are you saying that the child class gets casted to the parent class within the GetInput function? Meaning that the GetInput function does not support inheritance ? – mlam Nov 11 '19 at 16:21
  • Hi @mlam According to your "context", you set a type of "Textbook" to the "book", so it will cast the child class(Textbook) to the parent class(Book). The result is it will just have the fields in parent class. And I think there is nothing to do with the GetInput function, it is caused by designed(by your class structure and c# grammar) – Hury Shen Nov 12 '19 at 01:08
  • If the context has a type of `Textbook` why would it be casted to the parent class? – mlam Nov 12 '19 at 13:01
  • @HuryShen you turned this into a discussion on polymorphism. That's not what they're asking. The issue is an azure functions issue where you store an object in a context to move the object from one function to another. They're saying they aren't getting the subtype from context, only the super. – Matt M Nov 12 '19 at 14:48
  • @mlam I would update your question to show some functions-related code – Matt M Nov 12 '19 at 14:49
  • Hi @MattM But I think the nature of this problem is "if cast the child class to parent class, the class will just have the fields from the parent class". If you can help mlam to solve it, please feel free to provide your solution. Our purpose is to help him solve the problem, right ? – Hury Shen Nov 12 '19 at 15:02
0

Tracked the updates to pass in Json serialization to Azure Functions here showing that it will be in v2.1!

mlam
  • 31
  • 4
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. Please add some explanation and quotes. – Anna Dec 20 '19 at 00:52