3

I'll preface this with the fact that I'm trying to avoid using Newtonsoft.Json since, ostensibly, System.Text.Json is ready for prime-time in .NET 6.

So I have two enums coming from an API and I want to deserialise them using this test method:

[Theory]
[ClassData(typeof(TestDataGenerator))]
public void CanDeserialiseEnumsWithCustomJsonStrings(Enum expected, string jsonName)
{
    jsonName.ShouldNotBeNullOrEmpty();
    ReadOnlySpan<char> json = $"{{\"TestEnum\":\"{jsonName}\"}}";

    Type constructed = typeof(TestEnumWrapper<>).MakeGenericType(expected.GetType());
        
    var res = JsonSerializer.Deserialize(json, constructed);

    constructed.GetProperty("TestEnum").GetValue(res).ShouldBe(expected);
}

private class TestEnumWrapper<T> where T: struct
{
    public T TestEnum { get; set; }
}

(Yes I know that this could be done with JsonSerializer.Deserialize<T>(), I want to be able to test many types with this test so I need the reflection AFAICT).

The first one, works fine:

[JsonConverter(typeof(JsonStringEnumConverter))]
public enum RecordType
{
        [JsonPropertyName("country")]
        Country = 1,

        [JsonPropertyName("destinationOrbit")]
        DestinationOrbit = 2,

        [JsonPropertyName("engine")]
        Engine = 3,
        //etc...

}

The second one, fails on the deserialization, this seems to be due to the spaces in the names.

[JsonConverter(typeof(JsonStringEnumConverter))]
public enum ObjectClass
{
    [JsonPropertyName("Rocket Body")]
    RocketBody,
    [JsonPropertyName("Rocket Debris")]
    RocketDebris,
    [JsonPropertyName("Rocket Fragmentation Debris")]
    RocketFragmentationDebris,
    [JsonPropertyName("Rocket Mission Related Object")]
    RocketMissionRelatedObject,
    //etc...
}

The API is controlled by the European Space Agency, so somehow, I don't think I'll be able to persuade them to rationalise the response a bit more.

Is there any way around this?


Some have asked for an example of the JSON I'm trying to deserialise. I'm currently working on the Attributes part of this blob:

{
            "type": "object",
            "attributes": {
                "shape": null,
                "xSectMin": null,
                "satno": null,
                "depth": null,
                "objectClass": "Rocket Fragmentation Debris",
                "cosparId": null,
                "length": null,
                "height": null,
                "mass": null,
                "xSectMax": null,
                "vimpelId": 84303,
                "xSectAvg": null,
                "name": null
            },
            "relationships": {
                "states": {
                    "links": {
                        "self": "/api/objects/61345/relationships/states",
                        "related": "/api/objects/61345/states"
                    }
                },
                "initialOrbits": {
                    "links": {
                        "self": "/api/objects/61345/relationships/initial-orbits",
                        "related": "/api/objects/61345/initial-orbits"
                    }
                },
                "destinationOrbits": {
                    "links": {
                        "self": "/api/objects/61345/relationships/destination-orbits",
                        "related": "/api/objects/61345/destination-orbits"
                    }
                },
                "operators": {
                    "links": {
                        "self": "/api/objects/61345/relationships/operators",
                        "related": "/api/objects/61345/operators"
                    }
                },
                "launch": {
                    "links": {
                        "self": "/api/objects/61345/relationships/launch",
                        "related": "/api/objects/61345/launch"
                    }
                },
                "reentry": {
                    "links": {
                        "self": "/api/objects/61345/relationships/reentry",
                        "related": "/api/objects/61345/reentry"
                    }
                }
            },
            "id": "61345",
            "links": {
                "self": "/api/objects/61345"
            }
        }
10
  • 1
    Not supported by System.Text.Json. See: System.Text.Json: How do I specify a custom name for an enum value?, which has some suggested workarounds. Commented Dec 28, 2021 at 0:10
  • Deserializing RecordType works because, as explained in the docs, Reading is case insensitive. Writing can be customized by using a JsonNamingPolicy. Note that it's implied that reading cannot be customized by a JsonNamingPolicy. Commented Dec 28, 2021 at 0:15
  • 2
    Works fine in Newtonsoft.Json. Don't torture yourself. It's possible in System.Text.Json but requires lots of custom code or a Nuget. Commented Dec 28, 2021 at 0:25
  • Can you post the real json that you have pls? It is hard understand what is the problem, why you have to deserialize or serialize enums? Commented Dec 28, 2021 at 0:34
  • @Serge - Added :) Commented Dec 28, 2021 at 0:41

2 Answers 2

6

This is a solution for System.Text.Json

I used the Nuget package System.Text.Json.EnumExtensions
https://github.com/StefH/System.Text.Json.EnumExtensions

// you probably want these options somewhere global
private JsonSerializerOptions options;

private class TestEnumWrapper<T> where T : struct
{
    public T TestEnum { get; set; }
}

public enum ObjectClass
{
    [EnumMember(Value = "Rocket Body")]
    RocketBody,
    [EnumMember(Value = "Rocket Debris")]
    RocketDebris,
    [EnumMember(Value = "Rocket Fragmentation Debris")]
    RocketFragmentationDebris,
    [EnumMember(Value = "Rocket Mission Related Object")]
    RocketMissionRelatedObject,
    //etc...
}

private void CanDeserialiseEnumsWithCustomJsonStrings(Enum expected, string jsonName)
{
    var json = $"{{\"TestEnum\":\"{jsonName}\"}}";

    Type constructed = typeof(TestEnumWrapper<>).MakeGenericType(expected.GetType());

    var res = JsonSerializer.Deserialize(json, constructed, options);
}


public void Test()
{
    this.options = new JsonSerializerOptions();
    options.Converters.Add(new JsonStringEnumConverterWithAttributeSupport());

    var testSerialize = JsonSerializer.Serialize(new TestEnumWrapper<ObjectClass>() 
        { TestEnum = ObjectClass.RocketBody }, options);

    // Test Deserialize
    CanDeserialiseEnumsWithCustomJsonStrings(ObjectClass.RocketBody, "Rocket Body");
}

You only have to add the new JsonStringEnumConverterWithAttributeSupport() to the JsonSerializerOptions.Converters for this to work.

If you want to use the Converter as an Attribute you have to add an parameterless constructor to the class JsonStringEnumConverterWithAttributeSupport

public JsonStringEnumConverterWithAttributeSupport() : this(namingPolicy : null, allowIntegerValues : true,
    parseEnumMemberAttribute : true, parseDisplayAttribute : false, parseDescriptionAttribute : false)
{

}
Sign up to request clarification or add additional context in comments.

9 Comments

Going to leave it another hour or so to see if someone knows of a decent System.Text.Json option, if they don't, then I'll accept this one :) Although it would be good if you could add a short summary of what you've done because code-only answers aren't rated in these parts (even though I can see what you've done). Cheers bud
@Persistence i added a version for System.Text.Json
Thanking you my good sir, you are a scholar and a gent. Does it need the [JsonConverter(typeof(JsonStringEnumConverterWithAttributeSupport))] attribute?
@Persistence it works out of the box with the options like in my example, if you want to use the converter as an Attribute you have to add a parameterless constructor inside the Library class. (I just copied the 5 files from the nuget into my project, so i can edit them)
So I've just tested with this code here and I'm actually getting similar results to before I added the nuget, have I missed something? Thank you for being so helpful here gist.github.com/hughesjs/2a34b68588fd2fae3935abdda2dea2e4
|
2

try this, I uses Newtonsoft.Json and for test I deserialized only attributes since only they contain enum. You don't need any custom code.

var attributes= JsonConvert.DeserializeObject<Root>(json);

classes


    public enum ObjectClass
    {
    [EnumMember(Value = "Rocket Body")]
    RocketBody,
    [EnumMember(Value ="Rocket Debris")]
    RocketDebris,
    [EnumMember(Value = "Rocket Fragmentation Debris")]
    RocketFragmentationDebris,
    [EnumMember(Value ="Rocket Mission Related Object")]
    RocketMissionRelatedObject
    }

    public partial class Root
    {
        [JsonProperty("attributes")]
        public Attributes Attributes { get; set; }
    }

    public partial class Attributes
    {
        [JsonProperty("shape")]
        public object Shape { get; set; }

        [JsonProperty("xSectMin")]
        public object XSectMin { get; set; }

        [JsonProperty("satno")]
        public object Satno { get; set; }

        [JsonProperty("depth")]
        public object Depth { get; set; }

        [JsonProperty("objectClass")]
        public ObjectClass ObjectClass { get; set; }

        [JsonProperty("cosparId")]
        public object CosparId { get; set; }

        [JsonProperty("length")]
        public object Length { get; set; }

        [JsonProperty("height")]
        public object Height { get; set; }

        [JsonProperty("mass")]
        public object Mass { get; set; }

        [JsonProperty("xSectMax")]
        public object XSectMax { get; set; }

        [JsonProperty("vimpelId")]
        public long VimpelId { get; set; }

        [JsonProperty("xSectAvg")]
        public object XSectAvg { get; set; }

        [JsonProperty("name")]
        public string Name { get; set; }
    }

test

json=JsonConvert.SerializeObject(attributes); 
attributes= JsonConvert.DeserializeObject<Root>(json);

result

{
  "attributes": {
    "shape": null,
    "xSectMin": null,
    "satno": null,
    "depth": null,
    "objectClass": 2,
    "cosparId": null,
    "length": null,
    "height": null,
    "mass": null,
    "xSectMax": null,
    "vimpelId": 84303,
    "xSectAvg": null,
    "name": null
  }
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.