Home > Net >  Deserialise Enum From JSON String with Spaces
Deserialise Enum From JSON String with Spaces

Time:12-28

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"
            }
        }

CodePudding user response:

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)
{

}

CodePudding user response:

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
  }
}

  • Related