Search code examples
c#json.netgremlinnet

Map Gremlin result containing string arrays to C# class containing simple string properties


I am facing a problem while mapping a Gremlin result set to a class in C#. I am trying to get vertices along with properties. Here is the code:

public IList<T> All<T>() where T : class, new()
{
    Type type = typeof(T);
    string query = "g.V().hasLabel('" + type.Name.ToString().ToLower() + "').valueMap(true)";
    var resultSet = _gremlinClient.SubmitAsync<dynamic>(query).Result;
    List<T> list = JsonConvert.DeserializeObject<List<T>>(JsonConvert.SerializeObject(resultSet));
    return list;
}

And here is the User entity, which I am passing to this generic method.

public class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
}

When I run the code, it throws an error while deserializing.

'Unexpected character encountered while parsing value: [. Path '[0].FirstName', line 1, position 37.'

When I inspected it, I found that the JSON string has property values with square brackets, like this:

[
  {
    "id": 0,
    "label": "user",
    "FirstName": [ "Azhar" ],
    "LastName": [ "Rahi" ]
  },
  {
    "id": 3,
    "label": "user",
    "FirstName": [ "John" ],
    "LastName": [ "Doe" ]
  }
]

You can see some properties have square brackets like "FirstName":["Azhar"]. I have tested it without square brackets and it is working fine. So the reason for the error is because the strings are represented as arrays in the JSON.

In the above JSON string, id and label are auto-generated properties by Gremlin.Net. The rest are actually User object properties . I don't know why Gremlin adds brackets while adding properties to Vertex and if there is any possibility to avoid it.

Please suggest any solution, either in Gremlin.Net or by somehow changing the mapping of the JSON to the class.


Solution

  • So, to recap: you have some JSON for which some of the properties are arrays of strings, but you want to model them in your classes as single strings because the arrays should always have only one value.

    Json.Net will not handle this mapping by default as you have seen. But you can use a custom JsonConverter to bridge the gap. Define the converter like this:

    public class ArrayToSingleStringConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(string);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JToken token = JToken.Load(reader);
            switch (token.Type)
            {
                case JTokenType.Array:
                    return (string)token.Children<JValue>().FirstOrDefault();
                case JTokenType.String:
                    return (string)token;
                case JTokenType.Null:
                    return null;
                default:
                    throw new JsonException("Unexpected token type: " + token.Type);
            }
        }
    
        public override bool CanWrite => false;
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    

    Then, for each string property in your classes which might be an array in JSON, add a [JsonConverter] attribute specifying the ArrayToSingleStringConverter, like this:

    public class User
    {
        [JsonConverter(typeof(ArrayToSingleStringConverter))]
        public string FirstName { get; set; }
    
        [JsonConverter(typeof(ArrayToSingleStringConverter))]
        public string LastName { get; set; }
    }
    

    When you deserialize the JSON, it should now work like you want.
    Demo fiddle: https://dotnetfiddle.net/Q9sGja

    Note: If you want to handle all of your string properties in this manner, you can pass the converter to JsonConvert.DeserializeObject() instead of marking up your classes with [JsonConverter] attributes:

    List<T> list = JsonConvert.DeserializeObject<List<T>>(json, new ArrayToSingleStringConverter());
    

    Fiddle: https://dotnetfiddle.net/gcAqC5