Home > Software engineering >  Firebase returning miss-matched data types in JSON
Firebase returning miss-matched data types in JSON

Time:09-23

We are trying to retrieve data from FireBase as 2D array but we are getting miss-matched data types in JSON despite everything is set up the same way.

Data recieved:

[
  {"0":24, "1":74, "2":5, "8":5},
  {"0":45, "1":857, "10":7, "2":42, "3":8, "5":89, "55":98},
  [103, null, null, 195],
  null,
  null,
  null,
  {"0":84, "5":854, "6":425},
  {"6":8, "7":578},
  [0, 1],
  {"8":8, "9":9},
  [0, 1, 2, 3]
]

Data expected:

[
  [24, 74, 5, 5], 
  [45, 857, 7, 42, 8, 89, 98],
  [103, null, null, 195],
  null, 
  null,
  null, 
  [84, 854, 425], 
  [8, 578],
  [0, 1],
  [8, 9], 
  [0, 1, 2, 3]
]

Firebase setup:

enter image description here

We were wondering if there is any way to retrieve data from Firebase so that we are using the same type, and if not, how do we convert it to desired output in C# the most efficient way ?

CodePudding user response:

Sure it looks quite messy. The : {"0":24, "1":74, "2":5, "8":5} is a Dictionary<string,int>, using the object style serialization to ensure uniqueness of the keys.

Solution 1: Custom converter

You can solve it by using your custom converter. at each element of the main array :
JsonToken.Null, StartObject,StartArray.
You can choose the right deserialization and populate your main list with the selected elements.

In the following example I choosed List<List>> for ease of use. online demo

public class FireBaseResult
{
    public static List<FireBaseResultElement> FromJson(string json) => JsonConvert.DeserializeObject<List<FireBaseResultElement>>(json, Converter.Settings);
}

public partial struct FireBaseResultElement
{
    public long?[] InnerArray;
    public static implicit operator FireBaseResultElement(long?[]  Items) => new FireBaseResultElement{InnerArray = Items};
}

internal static class Converter
{
    public static readonly JsonSerializerSettings Settings =
        new JsonSerializerSettings{MetadataPropertyHandling = MetadataPropertyHandling.Ignore, DateParseHandling = DateParseHandling.None, 
                                    Converters = {new FireBaseResultElementConverter(), new IsoDateTimeConverter{DateTimeStyles = DateTimeStyles.AssumeUniversal}}, };
    
    
}

internal class FireBaseResultElementConverter : JsonConverter
{
    public override bool CanConvert(Type t) => t == typeof(FireBaseResultElement) || t == typeof(FireBaseResultElement? );
    public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
    {
        switch (reader.TokenType)
        {
            case JsonToken.Null:
                return new FireBaseResultElement{};
            case JsonToken.StartObject:
                var objectValue = serializer.Deserialize<Dictionary<string, long?>>(reader);
                return new FireBaseResultElement{InnerArray=objectValue.Values.ToArray()};
            case JsonToken.StartArray:
                var arrayValue = serializer.Deserialize<long?[]>(reader);
                return new FireBaseResultElement{InnerArray=arrayValue};
        }

        throw new Exception("Cannot unmarshal type FireBaseResultElement");
    }

    public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
    {
        throw new Exception("Cannot marshal type FireBaseResultElement");
    }
}

I need to wrap the InnerArray into a class or a struct in order to reference a custom converter on it.

Solution 2: Direct parsing with Newtonsoft.Json.Linq.

Using Newtonsoft.Json.Linq, you can directly parse the Json using the same technique.

List<List<int?>> result = new List<List<int?>>();
JArray o = JArray.Parse(jsonInput);

foreach (JToken item in o)
{
    switch (item.Type)
    {
        case JTokenType.Null:
            result.Add(null); // r.Add(new list<int>);
            break;
        case JTokenType.Object:
            var objectValue = item.ToObject<Dictionary<string, int?>>();
            result.Add(objectValue.Values.ToList());
            break;

        case JTokenType.Array:
            var arrayValue = item.ToObject<List<int?>>();
            result.Add(arrayValue);
            break;

        default:
            throw new Exception("Cannot unmarshal type FireBaseResultElement, unexpected JToken.Type");
            break;
    }

}
Console.WriteLine(new String('#', 9));
foreach (List<int?> row in result)
{
    IEnumerable<string> vals = row?.Select(x => x == null ? "null" : x.ToString()) ?? new[] { "null" };
    Console.WriteLine(String.Join(", ", vals));
}
  • Related