1

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.

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
azhar rahi
  • 331
  • 4
  • 16
  • `public string[] FirstName { get; set; }` – Gusman Jun 28 '20 at 17:02
  • why you serialize and de-serialize the same thing, return just `resultSet` – Mohammed Sajid Jun 28 '20 at 17:09
  • @Gusman I know that but logically FirstName of User cannot be an array. doing so will enforce me to get value from index 0 of array. 2ndly I am using Generic method, so the solution should he generic. I am going to update my description. – azhar rahi Jun 28 '20 at 17:25
  • @Sajid, that has thrown other errors. – azhar rahi Jun 28 '20 at 17:27
  • So if I understand correctly,in the `resultSet` you have an array of first and last name and you serialize and deserialize to avoid this result.if yes you could use JsonConverter. – Mohammed Sajid Jun 28 '20 at 17:38
  • No @Sajid I do not want to avoid anything... What I want is to map the FirstName, LastName to the User object i.e. need to parse the json into User Object (and set null for properties which do not exist in json) just like it happens in ASP.Net Web API when you post the data to controller with FormBody User – azhar rahi Jun 28 '20 at 19:06
  • Related: [JSON C# deserialize Array With 1 item to Object](https://stackoverflow.com/q/36797078/3744182). See `SimplePropertyArrayToSingleConverter` from [this answer](https://stackoverflow.com/a/36801978/3744182). – dbc Jun 29 '20 at 15:23

1 Answers1

1

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

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • Ok thanks Brian. Thats what I was also thinking. I will go for it. But strange thing is that why Gremlin sets property values with square brackets. It should be without brackets – azhar rahi Jun 28 '20 at 19:20
  • I don't know the answer to that; I'm not really familiar with Gremlin. But I do know my way around Json.Net so that is why I suggested this particular solution. – Brian Rogers Jun 28 '20 at 19:24
  • Yes your solution is pretty good. But I think I need to use Reflection to get whether the User object property is an array or not. Based on that, I will have to set the json object properties to the User object properties – azhar rahi Jun 28 '20 at 19:31
  • Json.Net already uses reflection; that is how it works. You don't need to do anything extra outside of what I told you. Either mark the string properties in your class with a converter attribute or pass the converter to DeserializeObject. The converter checks whether the corresponding property in the JSON is an array or not. If it is, it takes the first child; otherwise it just takes it as a string. – Brian Rogers Jun 28 '20 at 19:44
  • If you're saying that your class has string arrays (not simple strings) and the corresponding JSON properties are sometimes an array and sometimes a string, then that is a different problem and requires a different converter. See [How to handle both a single item and an array for the same property using JSON.net](https://stackoverflow.com/q/18994685/10263). But that is not what you described in your question. – Brian Rogers Jun 28 '20 at 19:49
  • If you look at JSON string in my question, you will find the same case where some properties are string and some are string arrays. Those which are simple strings are automatically created by Gremlin (i.e. id and label) and those which are string arrays are the ones added by user application using Gremlin.Net operations. – azhar rahi Jun 28 '20 at 20:30
  • As I have told, I have set the vertex property according to documentation but Gremlin sets the user's provided properties with brackets (may be Gremlin considers the user's provided data as arrays). I have read somewhere that this is the expected behavior of Gremlin. I am still trying to find Gremlin query which can retrieve the value as string instead of array (or without brackets if it has different meaning in Gremlin). – azhar rahi Jun 28 '20 at 20:30
  • OK, I think we are on the same page. Sorry, I was confused by an earlier comment. This answer should work for you. Give it a try. – Brian Rogers Jun 28 '20 at 22:16
  • Well that worked perfectly. Thanks. However if I find any straightforward solution, instead of adding attribute to properties, I will share it too. – azhar rahi Jun 28 '20 at 22:32
  • No problem; I'm glad I was able to help. – Brian Rogers Jun 29 '20 at 00:29