Search code examples
jsonmodel-view-controllertelerikcodefluent

CodeFluent JSON Serialization Does Not Work for All Fields


I'm using CodeFluent JsonUtilities to convert an object to JSON. Using anything else seems to have various other issues (e.g. Circular Referencing).

Here's some functions I use to convert to JSON for ASP.NET MVC, using CodeFluent.Runtime.Utilities namespace (for the JsonUtilities).

    public static ContentResult ConvertToJsonResponse(object obj)
    {
        string json = JsonUtilities.Serialize(obj);
        return PrepareJson(json);
    }

    /// <summary>
    /// Converts JSON string to a ContentResult object suitable as a response back to the client
    /// </summary>
    /// <param name="json"></param>
    /// <returns></returns>
    public static ContentResult PrepareJson(string json)
    {
        ContentResult content = new ContentResult();
        content.Content = json;
        content.ContentType = "application/json";

        return content;
    }

The problem is when I use JsonUtilities to convert an object it seems to have skipped some nested objects.

For example, I tried to convert DataSourceResult object (from Telerik) to JSON using CodeFluent.

public ActionResult UpdateTeam([DataSourceRequest]DataSourceRequest request, TeamViewModel teamViewModel)
{
    ModelState.AddModelError("", "An Error!");
    DataSourceResult dataSourceResult = new[] { teamViewModel }.ToDataSourceResult(request, ModelState);
    ContentResult ret = CodeFluentJson.ConvertToJsonResponse(dataSourceResult);
    return ret;
}

The dataSourceResult holds three main properties:

  1. Data - which hold my Model that contains my data.
  2. Total - which holds the amount of data objects there are.
  3. Errors - which holds all the errors of my MVC model. It is very nested, with a lot of properties under it.

When I try to use CodeFluent utilities to convert the DataSourceResult object it works to convert "Data", and "Total" fields, but with Errors, it skips it entirely, resulting in the below JSON string:

{  
   "Data":[  
      {  
         "ExampleProperty":"ExampleValue"
      }
   ],
   "Total":1,
   "AggregateResults":null,
   "Errors":{  }
}

I'm guessing the issue is with the "Errors" object being too nested for the CodeFluent converter. So my question is are there any CodeFluent serialization options/code I'm missing to work with heavily nested objects for JSON conversion?


Solution

  • The problem comes from the fact you're creating a model error with an empty key. It's not forbidden but JsonUtilities just skip dictionary values with an empty key, by design.

    Just use a real key, like this:

    ModelState.AddModelError("my first error", "An Error!");
    ModelState.AddModelError("my second error", "An Error!");
    

    And you'll see the errors collection serialized, like this:

    {
     "Data":[  
      {  
         "ExampleProperty":"ExampleValue"
      }],
     "Total": 1,
     "AggregateResults": null,
     "Errors": {
       "my first error": {
         "errors": [
           "An Error!"
          ]
        },
       "my second error": {
         "errors": [
           "An Error!"
          ]
        }
      }
    }
    

    Otherwise, if you really want to keep empty keys, then you can leverage the JsonUtilitiesOptions class that has callbacks for tweaking the serialization (and deserialization) process. Careful with this as you can easily break JSON syntax. This is how you could do it, in a way that should handle all cases:

    JsonUtilitiesOptions options = new JsonUtilitiesOptions();
    options.WriteValueCallback += (e) =>
    {
        IDictionary valueDic = e.Value as IDictionary;
        if (valueDic != null)
        {
            e.Writer.Write('{');
            bool first = true;
            foreach (DictionaryEntry entry in valueDic)
            {
                if (!first)
                {
                    e.Writer.Write(',');
                }
                else
                {
                    first = false;
                }
    
                // reuse JsonUtilities already written functions
                JsonUtilities.WriteString(e.Writer, entry.Key.ToString(), e.Options);
                e.Writer.Write(':');
                // object graph is for cyclic/infinite serialization checks
                JsonUtilities.WriteValue(e.Writer, entry.Value, e.ObjectGraph, e.Options);
            }
            e.Writer.Write('}');
            e.Handled = true; // ok, we did it
        }
    };
    string json = JsonUtilities.Serialize(obj, options);
    

    Now, you'll get this result:

    {
     "Data":[  
      {  
         "ExampleProperty":"ExampleValue"
      }],
     "Total": 1,
     "AggregateResults": null,
     "Errors": {
       "": {
         "errors": [
           "An Error!"
          ]
        }
      }
    }