Return Dictionary<string, object> that was serialized with newtonsoft

I am wrapping another API and need to mirror it’s output format. I have a tricky field containing unstructured data that I wrote a custom converter for in Newtonsoft to get it to parse. I initially made the property an ExpandoObject which worked fine but the typescript DTO generation doesn’t make the type any and I have to manually change it each time I generate DTO.

I found another post where you say it’s bad to return ExpandoObjects and to use Dictionary<string, object> instead but I am having some issues with that.

I have declared my property like so:

[JsonProperty("attrs")]
[JsonConverter(typeof(ArrayToNullConverter))]
 public Dictionary<string, object>? Attrs { get; set; }

I am able to serialize/deserialize it fine with Newtonsoft but if I try to return the object on my endpoint the ServiceStack serializer turns it into a series of nessted empty arrays.

var rootObject = JsonConvert.DeserializeObject<RootObject>(data);

Console.WriteLine("Newtonsoft serialization");
Console.WriteLine(JsonConvert.SerializeObject(rootObject, Formatting.Indented));
Console.WriteLine("ServiceStack serialization");
Console.WriteLine(rootObject.ToJson());
Output

Newtonsoft serialization
{
  "Blocks": [
    {
      "Name": "block1",
      "Type": "type1",
      "Attrs": {
        "key1": "value1",
        "key2": [
          {
            "nestedKey1": "nestedValue1",
            "nestedKey2": "nestedValue2"
          },
          "value3",
          "value4"
        ]
      }
    },
    {
      "Name": "block2",
      "Type": "type2",
      "Attrs": null
    },
    {
      "Name": "block3",
      "Type": "type3",
      "Attrs": null
    }
  ]
}
ServiceStack serialization
{"Blocks":[{"Name":"block1","Type":"type1","Attrs":{"key1":"value1","key2":[[[[]],[[]]],[],[]]}},{"Name":"block2","Type":"type2"},{"Name":"block3","Type":"type3"}]}

I think the issue is that nested objects and arrays are typed to Newtonsoft JObject or JArray which is causing problems but it also doesn’t return the property names.

I am trying to figure out how I can use JsConfig.SerializeFn to make it work but I can’t see how to do it. I always end up having to use an ExpandoObject and then looping over all properties to convert it to camel case.

Any help and advice appreciated.

Whenever you have any object it leaves a black hole in your Service Contract which you’re not going to be able to generate types for. You’ll be able to serialize a Dictionary<string,object> property but it can only be deserialized into untyped .NET collections using JavaScript Utils.

The only way you’re going to be able to generate types for it is if you convert it into a strong type and return that instead.

Mixing serialization models is always going to be a bad idea that will likely lead to future issues. If you’re wrapping an external API you may want to consider returning a JSON string using IRequiresRequestStream that uses Newtonsoft’s JSON serializer to serialize/deserialize inside your Service implementation. You’re still not going to be able to generate types for it but at least you’ll avoid serializer issues by using the same de/serializer implementation.

Returning a JSON string would be ideal. Are there any examples to follow of modifying the body? Do I have to construct all the headers manually? Sorry, I cant quite get the full understanding from the example given in docs.

You can just return a string with a MimeTypes.Json content type which you can add with the [JsonOnly] attribute, e.g:

//Request DTO
public class MyRequest : IReturn<string> {}

//...
public class MyServices : Service
{
    [JsonOnly]
    public object Any(MyRequest request)
    {
         string json = SerializeWithNewtonsoft(...);
         return json;
    }
}
1 Like

Ah my bad, I had missed the [JsonOnly] so it didn’t generate the headers.

Thanks for the amazing help as always, it’s working great now!

1 Like