0

I'm using newtonsoft json, and I need to serialize/deserialize this kind of json:

[
  {
    "value": "value1",
    "field": "field1"
  },
  {
    "value": "value2",
    "field": "field2"
  },
  {
    "value": "value3",
    "field": "field3"
  },
  [
    {
      "value": "value4",
      "field": "field4"
    },
    {
      "value": "value4",
      "field": "field5"
    }
  ]
]

As you can see, I can have an object or a list of this object at the same level. I've created a class for the object but I don't know how to make the serialization process this kind of json, or if it's possible to do.

EDIT: I don't have the choice to process that json as it comes to me. So please, if you think I like this, you're wrong.

ebelair
  • 844
  • 11
  • 27
  • Not a fan, but for serialization, put your objects and collections inside a `List` and ask newtonsoft.json to serialize that, should come close enough. If you want to consume such a JSON, you'll need to write a custom Converter (maybe to an object containing two properties : a `List`, and a `List>`, or you might decide to flatten all results) – Irwene Jul 04 '23 at 14:10
  • @Irwene This is the way I'm going to try, creating a kind of container that contains two properties for Item and List – ebelair Jul 04 '23 at 14:32
  • You might want to take a look at OneOf. This could get you a usable list of a single type which would still be strongly typed. Of course you'd need to put in the work to create the Converter to read the Json, but It could add some more utility than just a holder class – Irwene Jul 04 '23 at 14:41
  • 1
    Do you need to distinguish between an object and a list with a single object? If not, you could consider mapping your JSON to a `List>` where standalone objects get mapped to a list with one value. To do that, you can use one of the converters from [How to handle both a single item and an array for the same property using JSON.net](https://stackoverflow.com/q/18994685/3744182). – dbc Jul 04 '23 at 14:50
  • Yes I need to distinguish object and list, I think I found a good way to parse the json by creating an ItemContainer with two properties: List Items and List> NestedItems. Then I parse the JToken, then check the type and populate the right property. – ebelair Jul 05 '23 at 08:12

2 Answers2

1

you can create a custom json converter

Result result = JsonConvert.DeserializeObject<Result>(json, new ArrayJsonConverter())

public class ArrayJsonConverter : JsonConverter<Result>
{
    public override Result ReadJson(JsonReader reader, Type objectType, Result existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        JArray jArr = JArray.Load(reader);

        var result = new Result { Items = new List<Item>(), ListItems = new List<List<Item>>() };

        foreach (var item in jArr)
            if (item.Type == JTokenType.Array) result.ListItems.Add(item.ToObject<List<Item>>());
            else result.Items.Add(item.ToObject<Item>());

        return result;
    }

    public override void WriteJson(JsonWriter writer, Result value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

public class Result
{
    public List<Item> Items { get; set; }
    public List<List<Item>> ListItems { get; set; }
}

public class Item
{
    public string value { get; set; }
    public string field { get; set; }
}
Serge
  • 40,935
  • 4
  • 18
  • 45
  • Unfortunately, I need to preserve the separated structure of the values that are in list. Maybe I could create a kind of ItemContainer that can contain either an item or a List – ebelair Jul 04 '23 at 14:11
  • 1
    @ebelair You have to decide what you want and only after this to post your question. c# is not a javascript, using List is not the best solution. – Serge Jul 04 '23 at 14:12
  • This is not easy when I don't know what to decide, If I ask the question, it is because I really don't know how to code that ambiguous json. Anyway, your solution (after the edit) seems to be the good one, so I'll do it that way, now I have to serialize, I think it's not too hard to do. – ebelair Jul 05 '23 at 08:57
0

Here is the solution I found thanks to your help:

public class Item
{
    public string Value { get; set; }
    public string Field { get; set; }
}

public class ItemContainer
{
    public List<Item> Items { get; set; }
    public List<List<Item>> NestedItems { get; set; }
}

public class ItemContainerConverter : JsonConverter<ItemContainer>
{
    public override ItemContainer ReadJson(JsonReader reader, Type objectType, ItemContainer existingValue,
        bool hasExistingValue, JsonSerializer serializer)
    {
        var jsonObject = JArray.Load(reader);
        var itemContainer = new ItemContainer
        {
            Items = new List<Item>(),
            NestedItems = new List<List<Item>>()
        };

        foreach (var token in jsonObject)
        {
            if (token.Type == JTokenType.Array)
            {
                var nestedItemList = token.ToObject<List<Item>>();
                itemContainer.NestedItems.Add(nestedItemList);
            }
            else if (token.Type == JTokenType.Object)
            {
                var item = token.ToObject<Item>();
                itemContainer.Items.Add(item);
            }
        }

        return itemContainer;
    }

    public override void WriteJson(JsonWriter writer, ItemContainer value, JsonSerializer serializer)
    {
        writer.WriteStartArray();

        foreach (var item in value.Items)
        {
            serializer.Serialize(writer, item);
        }

        if (value.NestedItems != null && value.NestedItems.Any())
        {
            writer.WriteStartArray();
            foreach (var nestedItem in value.NestedItems.SelectMany(nestedList => nestedList))
            {
                serializer.Serialize(writer, nestedItem);
            }
            writer.WriteEndArray();
        }

        writer.WriteEndArray();
    }
}

And there is how to use it:

var settings = new JsonSerializerSettings()
{
    ContractResolver = new DefaultContractResolver
    {
        NamingStrategy = new SnakeCaseNamingStrategy()
    }
};
settings.Converters.Add(new ItemContainerConverter());

var rootObject = JsonConvert.DeserializeObject<ItemContainer>(jsonString, settings);
var json = JsonConvert.SerializeObject(rootObject, settings);
ebelair
  • 844
  • 11
  • 27