187

I'm using an MVC 4 web API and asp.net web forms 4.0 to build a rest API. It's working great:

[HttpGet]
public HttpResponseMessage Me(string hash)
{
    HttpResponseMessage httpResponseMessage;
    List<Something> somethings = ...

    httpResponseMessage = Request.CreateResponse(HttpStatusCode.OK, 
                                 new { result = true, somethings = somethings });

    return httpResponseMessage;
}

Now I need to prevent some properties to be serialized. I know I can use some LINQ over the list and get only the properties I need, and generally it's a good approach, but in the present scenario the something object is too complex, and I need a different set of properties in different methods, so it's easier to mark, at runtime, each property to be ignored.

Is there a way to do that?

user1330271
  • 2,601
  • 3
  • 20
  • 26
  • You can add ScriptIgnore to the property. view this question http://stackoverflow.com/questions/10169648/how-to-exclude-property-from-json-serialization – atbebtg Aug 07 '12 at 17:44

12 Answers12

255

ASP.NET Web API uses Json.Net as default formatter, so if your application just only uses JSON as data format, you can use [JsonIgnore] to ignore property for serialization:

public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }

    [JsonIgnore]
    public List<Something> Somethings { get; set; }
}

But, this way does not support XML format. So, in case your application has to support XML format more (or only support XML), instead of using Json.Net, you should use [DataContract] which supports both JSON and XML:

[DataContract]
public class Foo
{
    [DataMember]
    public int Id { get; set; }
    [DataMember]
    public string Name { get; set; }

    //Ignore by default
    public List<Something> Somethings { get; set; }
}

For more understanding, you can read the official article.

cuongle
  • 74,024
  • 28
  • 151
  • 206
  • I think I will have to find a way to add and remove attributes at run time using jsonignore. – user1330271 Aug 07 '12 at 17:57
  • WORKED LIKE A CHARM ! THANKS :) – Paulo Rodrigues May 19 '16 at 14:07
  • Why is it sad that JsonIgnore attribute is not supported with XML response? – Mukus Nov 08 '16 at 05:29
  • Datacontract is a great solution. It gives me a clean REST API. At the same time when I save the data in a no-sql, the ignored properties are persisted despite the objects being stored as json. – FrankyHollywood Mar 16 '18 at 09:45
  • What namespace is the JsonIgnore attribute in? – Fedor Alexander Steeman Apr 23 '18 at 12:51
  • 1
    @FedorSteeman The namespace of JsonIgnore is Newtonsoft.Json, needs JSON.Net-nuget package. DataContract and DataMember -attributes on the other hand need System.Runtime.Serialization-namespace (and reference if it's missing) – Esko Sep 11 '18 at 10:33
  • My main issue is that these configurations go away with every dbcontext scaffold in EF Core, does anyone have a workaround for that? Could these attributes be in a partial class, or be set programmatically? – Naner Apr 22 '19 at 12:45
  • Note JsonIgnore is still ok for the new System.Text.Json namespace: https://learn.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonignoreattribute w/o the old newtonsoft thing – Simon Mourier May 21 '20 at 15:18
  • JsonIgnore doesn't work if you require it for input but don't want it in output, i.e. password fields. – Dissident Rage Sep 16 '20 at 16:41
120

According to the Web API documentation page JSON and XML Serialization in ASP.NET Web API to explicitly prevent serialization on a property you can either use [JsonIgnore] for the Json serializer or [IgnoreDataMember] for the default XML serializer.

However in testing I have noticed that [IgnoreDataMember] prevents serialization for both XML and Json requests, so I would recommend using that rather than decorating a property with multiple attributes.

Michael Mason
  • 1,380
  • 1
  • 8
  • 10
  • 2
    This is the better answer. It covers XML and JSON with one attribute. – Oliver Aug 21 '13 at 14:13
  • 17
    Sadly `[IgnoreDataMember]` does not appear to work with lazy-loaded EF 6 proxy objects (virtual properties). `[DataContract]` and `[DataMember]` do, however. – Nick Aug 01 '14 at 15:30
36

Instead of letting everything get serialized by default, you can take the "opt-in" approach. In this scenario, only the properties you specify are allowed to be serialized. You do this with the DataContractAttribute and DataMemberAttribute, found in the System.Runtime.Serialization namespace.

The DataContactAttribute is applied to the class, and the DataMemberAttribute is applied to each member you want to be serialized:

[DataContract]
public class MyClass {

  [DataMember]
  public int Id { get; set;} // Serialized

  [DataMember]
  public string Name { get; set; } // Serialized

  public string DontExposeMe { get; set; } // Will not be serialized
}

Dare I say this is a better approach because it forces you to make explicit decisions about what will or will not make it through serialization. It also allows your model classes to live in a project by themselves, without taking a dependency on JSON.net just because somewhere else you happen to be serializing them with JSON.net.

Kols
  • 3,641
  • 2
  • 34
  • 42
CBono
  • 3,803
  • 1
  • 32
  • 40
  • 2
    The only approach that worked out of the box with .Net Core to hide inheritted members. Works for both XML and Json serialization. Kudos – Piou Oct 20 '16 at 22:14
  • I need the same funcionality but properties are included or excluded depends on which api method is invoked i.e different data are needed for different api calls. Any suggestions – Beingnin Feb 15 '18 at 03:08
  • This works great, but my main issue is that these configurations go away with every dbcontext scaffold in EF Core, does anyone have a workaround for that? Could these attributes be in a partial class, or be set programmatically? – Naner Apr 22 '19 at 12:42
21

This worked for me: Create a custom contract resolver which has a public property called AllowList of string array type. In your action, modify that property depending on what the action needs to return.

1. create a custom contract resolver:

public class PublicDomainJsonContractResolverOptIn : DefaultContractResolver
{
    public string[] AllowList { get; set; }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);

        properties = properties.Where(p => AllowList.Contains(p.PropertyName)).ToList();
        return properties;
    }
}

2. use custom contract resolver in action

[HttpGet]
public BinaryImage Single(int key)
{
    //limit properties that are sent on wire for this request specifically
    var contractResolver = Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver as PublicDomainJsonContractResolverOptIn;
    if (contractResolver != null)
        contractResolver.AllowList = new string[] { "Id", "Bytes", "MimeType", "Width", "Height" };

    BinaryImage image = new BinaryImage { Id = 1 };
    //etc. etc.
    return image;
}

This approach allowed me to allow/disallow for specific request instead of modifying the class definition. And if you don't need XML serialization, don't forget to turn it off in your App_Start\WebApiConfig.cs or your API will return blocked properties if the client requests xml instead of json.

//remove xml serialization
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
joym8
  • 4,014
  • 3
  • 50
  • 93
  • Something must have changed with newer versions but I was unable to get this to work. I could get it to work by doing 'new' instead of 'as' when modifing the resolver. JsonContractResolver type is not compatable for some reason. The issue with doing new is that it overwrites it for all instead of just the one. – Kalel Wade Feb 05 '14 at 18:01
  • I managed to get this working by using Request.CreateResponse() method that receives a MediaTypeFormatter, like this: var jsonMediaTypeFormatter = new JsonMediaTypeFormatter { SerializerSettings = new JsonSerializerSettings { ContractResolver = new PublicDomainJsonContractResolverOptIn { AllowList = new string[] { "Id", "Bytes", "MimeType", "Width", "Height" } } } }; return Request.CreateResponse(HttpStatusCode.OK, image, jsonMediaTypeFormatter); – Paul Sep 30 '14 at 12:28
  • What if we also want the blocked properties to be ignored in an XML response? – Carlos P Dec 06 '15 at 16:14
  • Unless data contract resolver is assigned once per request, this is not thread safe. I think this is assigned once, in the startup class. – Sprague May 06 '16 at 10:15
  • 2
    Even worse, ive tested this and the createproperties call is cached by the contract resolver. This answer is naive at best, dangerous at worst. – Sprague May 06 '16 at 10:20
19

I will show you 2 ways to accomplish what you want:

First way: Decorate your field with JsonProperty attribute in order to skip the serialization of that field if it is null.

public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }

    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public List<Something> Somethings { get; set; }
}

Second way: If you are negotiation with some complex scenarios then you could use the Web Api convention ("ShouldSerialize") in order to skip serialization of that field depending of some specific logic.

public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }

    public List<Something> Somethings { get; set; }

    public bool ShouldSerializeSomethings() {
         var resultOfSomeLogic = false;
         return resultOfSomeLogic; 
    }
}

WebApi uses JSON.Net and it use reflection to serialization so when it has detected (for instance) the ShouldSerializeFieldX() method the field with name FieldX will not be serialized.

foxhard
  • 496
  • 4
  • 8
  • It's not done by web api, web api uses Json.NET by default for serializing. This process is done by Json.NET not web api – Hamid Pourjam Jan 02 '16 at 11:40
  • 1
    Second solution is nice because it allows to keep the Domain object technology agnostic without the need of rewriting DTOs just to hide some fields. – Raffaeu Jun 22 '18 at 11:11
17

I'm late to the game, but an anonymous objects would do the trick:

[HttpGet]
public HttpResponseMessage Me(string hash)
{
    HttpResponseMessage httpResponseMessage;
    List<Something> somethings = ...

    var returnObjects = somethings.Select(x => new {
        Id = x.Id,
        OtherField = x.OtherField
    });

    httpResponseMessage = Request.CreateResponse(HttpStatusCode.OK, 
                                 new { result = true, somethings = returnObjects });

    return httpResponseMessage;
}
Tim Hoolihan
  • 12,316
  • 3
  • 41
  • 54
13

Try using IgnoreDataMember property

public class Foo
    {
        [IgnoreDataMember]
        public int Id { get; set; }
        public string Name { get; set; }
    }
Dan Drews
  • 1,966
  • 17
  • 38
Kavi
  • 181
  • 1
  • 6
9

Works fine by just adding the: [IgnoreDataMember]

On top of the propertyp, like:

public class UserSettingsModel
{
    public string UserName { get; set; }
    [IgnoreDataMember]
    public DateTime Created { get; set; }
}

This works with ApiController. The code:

[Route("api/Context/UserSettings")]
    [HttpGet, HttpPost]
    public UserSettingsModel UserSettings()
    {
        return _contextService.GetUserSettings();
    }
Dannejaha
  • 99
  • 1
  • 1
  • Also maybe a better solution is to isolate the View-Models from the "Back end" models, so you can skip this declaration. I find myself better in that situation often. – Dannejaha Aug 20 '19 at 18:49
6

Almost same as greatbear302's answer, but i create ContractResolver per request.

1) Create a custom ContractResolver

public class MyJsonContractResolver : DefaultContractResolver
{
    public List<Tuple<string, string>> ExcludeProperties { get; set; }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        if (ExcludeProperties?.FirstOrDefault(
            s => s.Item2 == member.Name && s.Item1 == member.DeclaringType.Name) != null)
        {
            property.ShouldSerialize = instance => { return false; };
        }

        return property;
    }
}

2) Use custom contract resolver in action

public async Task<IActionResult> Sites()
{
    var items = await db.Sites.GetManyAsync();

    return Json(items.ToList(), new JsonSerializerSettings
    {
        ContractResolver = new MyJsonContractResolver()
        {
            ExcludeProperties = new List<Tuple<string, string>>
            {
                Tuple.Create("Site", "Name"),
                Tuple.Create("<TypeName>", "<MemberName>"),
            }
        }
    });
}

Edit:

It didn't work as expected(isolate resolver per request). I'll use anonymous objects.

public async Task<IActionResult> Sites()
{
    var items = await db.Sites.GetManyAsync();

    return Json(items.Select(s => new
    {
        s.ID,
        s.DisplayName,
        s.Url,
        UrlAlias = s.Url,
        NestedItems = s.NestedItems.Select(ni => new
        {
            ni.Name,
            ni.OrdeIndex,
            ni.Enabled,
        }),
    }));
}
tsu1980
  • 2,472
  • 1
  • 25
  • 13
4

You might be able to use AutoMapper and use the .Ignore() mapping and then send the mapped object

CreateMap<Foo, Foo>().ForMember(x => x.Bar, opt => opt.Ignore());
kenwarner
  • 28,650
  • 28
  • 130
  • 173
4

For .NET Core 3.0 and above:

The default JSON serializer for ASP.NET Core is now System.Text.Json, which is new in .NET Core 3.0. Consider using System.Text.Json when possible. It's high-performance and doesn't require an additional library dependency.

https://learn.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.1&tabs=visual-studio#newtonsoftjson-jsonnet-support

Sample (Thanks cuongle)

using System.Text.Json.Serialization;

public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }

    [JsonIgnore]
    public List<Something> Somethings { get; set; }
}

If you already have Newtonsoft.Json intalled and chose to use it instead, by default, [JsonIgnore] won't work as expected.

themefield
  • 3,847
  • 30
  • 32
0

For some reason [IgnoreDataMember] does not always work for me, and I sometimes get StackOverflowException (or similar). So instead (or in addition) i've started using a pattern looking something like this when POSTing in Objects to my API:

[Route("api/myroute")]
[AcceptVerbs("POST")]
public IHttpActionResult PostMyObject(JObject myObject)
{
    MyObject myObjectConverted = myObject.ToObject<MyObject>();

    //Do some stuff with the object

    return Ok(myObjectConverted);
}

So basically i pass in an JObject and convert it after it has been recieved to aviod problems caused by the built-in serializer that sometimes cause an infinite loop while parsing the objects.

If someone know a reason that this is in any way a bad idea, please let me know.

It may be worth noting that it is the following code for an EntityFramework Class-property that causes the problem (If two classes refer to each-other):

[Serializable]
public partial class MyObject
{
   [IgnoreDataMember]
   public MyOtherObject MyOtherObject => MyOtherObject.GetById(MyOtherObjectId);
}

[Serializable]
public partial class MyOtherObject
{
   [IgnoreDataMember]
   public List<MyObject> MyObjects => MyObject.GetByMyOtherObjectId(Id);
}
Arg0n
  • 8,283
  • 2
  • 21
  • 38