skip to Main Content

I would like to accept an object of a known type where one of the properties is simply a Record<string, any>. Currently the only way I can get this to work is to force my client to JSON.stringify the property and then I can deserialize it just fine. The issue is that that’s not necessary with any other framework I’ve ever used, so I’d like to allow the client to just pass JSON.

Here is what I’d like to pass, pretending we have events that we POST about specific objects in a system:

{
    "eventType" : "created",
    "objectId" : "cb01e673-3835-44a8-a803-7cbfd437f0e2",
    "eventDetails" : 
    {
        "anyKey" : "someValue",
        "someArray" : ["no problem!", "no reason this should not work!"],
        "someNestedObject" : 
        {
            "anyNestedKey" : "should never be an issue"
        }
    }
}

Here is my request object:

public class ObjectEvent
{  
    public string? ObjectId { get; set; }

    public string? EventType { get; set; }
    public dynamic? EventDetails { get; set; } 
}

Problem is, when I get EventDetails it looks like this:

    Request ValueKind = Object : "{         "whatever": "go for it"     }"  

This means it’s now a JsonElement, but that’s not true. It was just a Json object passed from the client. I can SEE the object there but I cannot seem to get it into a dynamic.

Again, if I force everyone to serialize the "EventDetails" property as a string before POSTing, I can switch "dynamic? EventDetails" to "string? EventDetails" and use Newtonsoft.Deserialize and it works fine, but that’s not really acceptable when people are using curl and postman to access this or writing jest tests. They should be able to pass an object and .NET should accept it, which it kind of is but not in a way that I can use.

Any suggestions? Thank you in advance.

2

Answers


  1. Chosen as BEST ANSWER

    I figured it out, much simpler than it might appear!

    Given that I have a RequestModel and then a Model that I'm trying to marshal the data to for business logic:

    public class EventCreateRequest
    { 
        public string? EventType { get; set; }
        public object? EventDetails { get; set; } 
    }
    
    public class EventCreateModel
    { 
        public EventTypes EventType { get; set; }
        public ExpandoObject? EventDetails { get; set; } 
    }
    

    Then the adapter looks like this:

    
    public EventCreateModel convertFromCreateRequestToCreateModel(EventCreateRequest request)
    { 
        EventCreateModel model = new EventCreateModel()
        {
            EventType = request.EventType != null ? Enum.Parse<EventTypes>(request.EventType) : null;
     
            EventDetails = JsonConvert.DeserializeObject<ExpandoObject>(request.EventDetails.ToString(), new ExpandoObjectConverter());
        } 
        return model;
    } 
    

  2. First off, I’d be questioning the design of an API where "anything goes" is a good idea. Usually there are some limitations that can/should be in place (e.g. level of nesting).

    Other point: using dynamic is in no way different than using object – except the point at which any errors are thrown compile time for object vs runtime for dynamic. And even if dynamic would be giving some benefits, you have no way of knowing at design time what will be inside (if you did, you could use that knowledge to shape the class definition instead of using dynamic/object).

    Anyway as to a proposed solution: check the docs for System.Text.Json for handling case of overflow JSON – i.e. anything that is not explicitly defined. (If you are using Newtonsoft.Json then either migrate (based on which version of .net are you using) or try finding the equivalent way for that library.

    In the end you will need to work with the JsonElement class anyway. As you write yourself:

    This means it’s now a JsonElement, but that’s not true. It was just a Json object passed from the client. I can SEE the object there but I cannot seem to get it into a dynamic.

    The bold text is true, the text in italics is not: it is JsonElement and you have it in the dynamic and code like this will work just fine:

    var jsonDetails = (JsonElement)EventDetails;
    

    or just change the class definition:

    public JsonElement? EventDetails { get; set; }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search