Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enum Converters - Unknown Value Handling #1361

Open
TonyValenti opened this issue Jun 28, 2017 · 4 comments
Open

Enum Converters - Unknown Value Handling #1361

TonyValenti opened this issue Jun 28, 2017 · 4 comments

Comments

@TonyValenti
Copy link

TonyValenti commented Jun 28, 2017

Hi Newtonsoft,
I'm not sure if this is a bug or a feature request, but here goes:
I am building an app that connects to a third-party service. In lots of places, the service returns string constants that can better be represented as Enums. New values aren't added often, but they are added on occasion, and when that happens, I don't want my code to start crashing. I want to just be able to deserialize the unknown enum as some other value.

I've tried using DefaultValue and other methods to no avail. StackOverflow seems to indicate that you have to roll your own converter to make this happen but that seems like overkill.

I've noticed that JsonSerializerSettings has a lot of properties that control how different types are deserialized but none have anything to do with Enums.

Perhaps I'm doing something wrong, but if not, I propose adding the following member:
JsonSerializerSettings.UnknownEnumValueHandling with the options of:
ThrowException - The current default behavior - an exception is thrown.
Ignore - the enum value is ignored.
UseDefault - The enum's default value is used. This should pick up the value from a DefaultValue attribute or alternately, perhaps a new UnknownValue attribute that can be applied to members or enums.

It would also be great if it could convert Enums that have underscores in the values. For example,

in_progress (string) -> Status.InProgress (enum)

Below is the code that gives a small example of the problem.

Source/destination types

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.ComponentModel;

namespace ConsoleApp7 {
    class Program {
        static void Main(string[] args) {
            var Contents = new[]{

                @"{""Status"": ""Open""}"
                , @"{""Status"": ""open""}"
                , @"{""Status"": ""closed""}"
                , @"{""Status"": ""Pizza""}"
            };


            var Settings = new Newtonsoft.Json.JsonSerializerSettings();
            Settings.DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Populate;
            Settings.DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.

            foreach(var item in Contents) {
                try {
                    var Matter = Newtonsoft.Json.JsonConvert.DeserializeObject<Matter>(item, Settings);
                } catch (Exception ex) {

                }
            }


        }
    }

    [DefaultValue(MatterStatus.Unknown)]
    public enum MatterStatus {
        Unknown,
        Open,
        Closed,
        Pending,
    }

    public class Matter {
        [DefaultValue(MatterStatus.Unknown)]
        public MatterStatus Status { get; set; }
    }

}
@crozone
Copy link

crozone commented Oct 25, 2017

I ended up hitting a very similar scenario recently. I have a web service that returns a json containing an array of strings, and each of those strings corresponds to an enum value. However, the client program that consumes that API endpoint only needs to know a small subset of those strings, which are conveniently represented as enums in my code (using the [EnumMember(Value = "actual_string_representation")] to define the string representation of the enum). The built in StringEnumConverter throws an exception whenever it hits one of the values that aren't defined, which isn't ideal.

My current solution is to use a class called TolerantEnumConverter, and use it to deserialize the array with the attribute [JsonProperty(PropertyName = "my_enums", ItemConverterType = typeof(TolerantEnumConverter))]

The code for the TolerantEnumConverter can be found here:

https://gist.github.com/gubenkoved/999eb73e227b7063a67a50401578c3a7

I agree with you that using this is only a stopgap, it's pretty overkill since it mostly just uses code from StringEnumConverter anyway.

In order for this functionality to be built into Json.Net, I think StringEnumConverter would need to be modified in order to accept the "UnknownEnumValueHandling" parameter and select the default value. The TolerantEnumConverter above contains some pretty good code which could be spliced in - mainly in the way it selects the default value:

If the enum is nullable, the then null is used as the default.

If any of the enums are named "Unknown", they are used as the default.

Otherwise, the first enum is used as the default. This should be modified to also take into account and prioritise the [DefaultValue] attribute.

Another thing: How would the UnknownEnumValueHandling.Ignore setting be interpreted? I don't think it's actually possible to "Ignore" an enum, since it has so be set to something during deserialization.

@crozone
Copy link

crozone commented Oct 25, 2017

It would also be great if it could convert Enums that have underscores in the values. For example, in_progress (string) -> Status.InProgress (enum)

If I understand what you're trying to do correctly, this is already possible by putting the [EnumMember] attribute above where the enum is defined, like so:

enum SomeEnum {
    [EnumMember(Value = "some_body")]
    SomeBody,
    [EnumMember(Value = "once_told_me")]
    OnceToldMe,
    [EnumMember(Value = "that_enums")]
    ThatEnums,
    [EnumMember(Value = "were_gonna_role_me")]
    WereGonnaRollMe
}

The StringEnumConverter will detected the [EnumMember] attribute and look for the string value defined there, instead of using the default string representation of the enum.

@ElvenMonky
Copy link

Any updates on this issue? Is it even considered? Still have to inherit from StringEnumConverter to support DefaultValue.

@cmpute
Copy link

cmpute commented Jan 29, 2018

Any updates? It's quite useful for me. If being able, I will implement this while solving my problem here at #1597

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants