Nishan
Nishan

Reputation: 4421

dotnet core web API JSON response has unnecessary elements

I'm lazy loading my table data to a JSON result and wending them to front end application. but when I get those data I notice that there are unnecessary elements, empty elements present in that response. And because of that my PUT or update operation do not work for these inner JSON properties.

   {
           "image":null,
           "paragraph":null,
           "question":{
              "grid":null,
              "elements":[

              ],
              "offeredAnswers":[

              ],
              "lazyLoader":{

              },
              "id":"1",
              "text":"How can we serve you better?",
              "type":"textarea",
              "questionRequired":false,
              "pageFlowModifier":false,
              "gridId":null,
              "min":null,
              "max":null
           },
           "lazyLoader":{

           }
   }

If I change the value of text, it will not get updated, but if i change paragraph then it will be changed in the database.

Here there's a new property called lazyLoader, I need to get rid of that too. and elements, offeredAnswers are really not needed since they are empty. I achived lazy loading by adding virtual keyword to referenced classes.

public partial class Questions
{
    public Questions()
    {
        Elements = new HashSet<Elements>();
        OfferedAnswers = new HashSet<OfferedAnswers>();
    }


    public string Id { get; set; }
    public string Text { get; set; }
    public string Type { get; set; }
    public bool QuestionRequired { get; set; }
    public bool PageFlowModifier { get; set; }
    public int? GridId { get; set; }
    public long? Min { get; set; }
    public long? Max { get; set; }

    public virtual Grids Grid { get; set; }
    public virtual ICollection<Elements> Elements { get; set; }
    public virtual ICollection<OfferedAnswers> OfferedAnswers { get; set; }
}

And I have this line in the Startup.cs file to stop reference loop handling because Without that the POST operation do not work since the JSON object I'm posting is quite complex and has reference loops inside of it.

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
                .AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);

And I have enabled Lazy Loading Proxies.

services.AddDbContext<RDS_Context>
            (options => options.UseLazyLoadingProxies().UseSqlServer(connection));

Upvotes: 2

Views: 1751

Answers (4)

eduardomps
eduardomps

Reputation: 41

Based on a suggestion at EFCore repos and another SO answer it referenced , I'll leave this alternative approach for those who just want to get rid of the lazyLoader attribute

Create a CustomContractResolver.cs

public class CustomContractResolver : DefaultContractResolver
{
    public static CustomContractResolver Instance { get; } = new CustomContractResolver();

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);
        if (member.Name == "LazyLoader")
        {
            property.Ignored = true;
        }
        return property;
    }

}

And modify your Startup.cs, ConfigureServices method to use your ContractResolver

services
    .AddMvc(...)
    .AddJsonOptions(options => {
        options.SerializerSettings.ContractResolver = CustomContractResolver.Instance;
    });

Upvotes: 1

user3163495
user3163495

Reputation: 3538

In Entity Framework, if you have an object where one or more of its properties use lazy loading, check its runtime type name using GetType().Name. For an object of a Car class, for example, you will notice that the runtime type is actually something called CarProxy, which is a temporary in-memory type created by Entity Framework using reflection. This "fake" proxy class's base type is Car, and has all the original Car properties, but includes an extra one called LazyLoader for properties that may need it.

If you do further checking on this "fake" CarProxy type, you will also see that Assembly.IsDynamic = true, which is indicative that the class was created dynamically using reflection (see documentation):

var TheCar = DBContext.Cars.Find(1);
Console.WriteLine(TheCar.GetType().Assembly.IsDynamic.ToString()); //will echo "true"

Luckily, Newtonsoft.Json has an override on the JsonConvert.SerializeObject() method that allows us to provide a base type, so that the resulting JSON doesn't contain properties that don't exist in that type. So, to eliminate the LazyLoader property, just provide the object's BaseType as the type parameter:

var TheCar = DBContext.Cars.Find(1);
var TheJSON = Newtonsoft.Json.JsonConvert.SerializeObject(TheCar, TheCar.GetType().BaseType);

To make sure you don't get any circular reference loops when serializing (a very high probability when using lazy loading), call the serializer with the following setting:

var TheCar = DBContext.Cars.Find(1);
var Settings = new Newtonsoft.Json.JsonSerializerSettings
{
    ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
};
var TheJSON = Newtonsoft.Json.JsonConvert.SerializeObject(TheCar, TheCar.GetType().BaseType, Settings);

Note: This may only work on the first level deep when the serializer travels through the object. If there are yet more lazy-loading child properties of the object you provide to the serializer, the "LazyLoader" property may appear again. I haven't tested it so I can't say for sure.

Upvotes: 1

er-sho
er-sho

Reputation: 9771

You can remove your empty Object and Array from json by iterating JProperty of JObject like

class Program
{
    static void Main(string[] args)
    {
        string json = File.ReadAllText(@"C:\Users\xxx\source\repos\ConsoleApp4\ConsoleApp4\Files\json6.json");

        JObject data = JObject.Parse(json);

        //Getting all those children that have value are empty from outer json
        var result1 = data.Children<JProperty>()
             .Where(x => (x.Value.Type == JTokenType.Object) && !x.Value.HasValues)
            .ToList();

        //Getting all those children that have value are empty from "question" object
        var result2 = data["question"].Children<JProperty>()
            .Where(x => (x.Value.Type == JTokenType.Object && !x.Value.HasValues) || (x.Value.Type == JTokenType.Array && !x.Value.HasValues))
            .ToList();


        //Remove all above empty object or arrays
        result1.ForEach(x => x.Remove());
        result2.ForEach(x => x.Remove());

        var obj = data.ToObject<JObject>();

        Console.WriteLine(obj);

        Console.ReadLine();
    }
}    

Output:

enter image description here

Note: If you want to remove only empty lazyLoader object throughout the json then use below lines in above code.

//Getting "lazyLoader" children that have value are empty from outer json
var result1 = data.Children<JProperty>()
     .Where(x => x.Value.Type == JTokenType.Object && !x.Value.HasValues && x.Name == "lazyLoader")
     .ToList();

//Getting "lazyLoader" children that have value are empty from "question" object
var result2 = data["question"].Children<JProperty>()
     .Where(x => (x.Value.Type == JTokenType.Object && !x.Value.HasValues && x.Name == "lazyLoader"))
     .ToList();

Output:

enter image description here

Upvotes: 1

kaffekopp
kaffekopp

Reputation: 2619

You could separate you database entity model from the model you would like to show in your front end. Map the properties of the entity model you need and want to include into your new front end model.

A quicker way of doing without implementing a new model can be by using anonymous objects, like:

myDbContext.MyObjects.Select(x => new { Prop1 = x.Property1, Prop2 = x.Property2 });

If you really have the LazyLoader property in your entity class (eg. injecting ILazyLoader as described here), you could instead decorate it with the JsonIgnore attribtue.

Upvotes: 3

Related Questions