0

I have list of Property classes that looks like this:

public class RootObject
{
    public List<Property> Properties { get; set; }
}

public class Property
{
    public string MyFirstProp { get; set; }
    public string MySecondProp {get; set; }
}

When serialized using Json.NET, the output is this:

{
   "Properties":[
      {
         "MyFirstProp":"Hello",
         "MySecondProp":"World"
      }
   ]
}

I would need the output to look like this:

{
   "Properties":[
      {
         "MyFirstProp":"Hello"
      },
      {
         "MySecondProp":"World"
      }
   ]
}

How can I accomplish this?

dbc
  • 104,963
  • 20
  • 228
  • 340
ManicKen
  • 21
  • 3
  • What do you want to do if your `Properties` list has more than one entry? Your needed output only makes sense when there is one `Property` to output. – dbc May 23 '22 at 18:13
  • There is only one Property to output. Why it is that way I'm not sure, but this is what is requested. :) – ManicKen May 23 '22 at 18:26
  • Then why is it a `List`? Can you change the `RootObject` class that contains the ` public List Properties { get; set; }` property? And do you need to deserialize, or only serialize? – dbc May 23 '22 at 18:35
  • I think the List is there to output an Json array; I can control the RootObject class. There's only need for serialization. – ManicKen May 23 '22 at 18:48
  • Related but not quite duplicate: [Serialize an object's properties to separate JSON objects in an array](https://stackoverflow.com/q/36201875/3744182). – dbc May 23 '22 at 20:00

2 Answers2

0

You can introduce a custom JsonConverter<Property> that serializes Property instances as arrays of single-property objects, rather than as a single object:

public class ObjectAsObjectArrayConverter<TObject> : JsonConverter<TObject>
{
    public override void WriteJson(JsonWriter writer, TObject value, JsonSerializer serializer)
    {
        var contract = (serializer.ContractResolver.ResolveContract(value.GetType()) as JsonObjectContract) ?? throw new ArgumentException("Wrong contract type");
        writer.WriteStartArray();
        foreach (var property in contract.Properties.Where(p => ShouldSerialize(p, value)))
        {
            var propertyValue = property.ValueProvider.GetValue(value);
            if (propertyValue == null && (serializer.NullValueHandling == NullValueHandling.Ignore || property.NullValueHandling == NullValueHandling.Ignore))
                continue;
            writer.WriteStartObject();
            writer.WritePropertyName(property.PropertyName);
            if (propertyValue == null)
                writer.WriteNull();
            else if (property.Converter != null && property.Converter.CanWrite)
                property.Converter.WriteJson(writer, propertyValue, serializer);
            else
                serializer.Serialize(writer, propertyValue);
            writer.WriteEndObject();
        }
        writer.WriteEndArray();
    }

    protected virtual bool ShouldSerialize(JsonProperty property, object value) =>
        property.Readable && !property.Ignored && (property.ShouldSerialize == null || property.ShouldSerialize(value));

    public override TObject ReadJson(JsonReader reader, Type objectType, TObject existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        if (existingValue == null)
            existingValue = (TObject)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();

        switch (reader.MoveToContentAndAssert().TokenType)
        {
            case JsonToken.Null:
                return (TObject)(object)null;
        
            case JsonToken.StartArray:
                while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndArray)
                {
                    switch (reader.TokenType)
                    {
                        case JsonToken.StartObject:
                            serializer.Populate(reader, existingValue);
                            break;
                        default:
                            throw new JsonSerializationException("Unexpected token type " + reader.TokenType.ToString());
                    }
                }
                break;

            case JsonToken.StartObject:
                serializer.Populate(reader, existingValue);
                break;

            default:
                throw new JsonSerializationException("Unexpected token type " + reader.TokenType.ToString());
        }
        return existingValue;
    }
}

public static partial class JsonExtensions
{
    public static JsonReader ReadToContentAndAssert(this JsonReader reader) =>
        reader.ReadAndAssert().MoveToContentAndAssert();

    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
}

Then modify RootObject as follows, replacing public List<Property> Properties with a single public Property Properties:

public class RootObject
{
    public Property Properties { get; set; }
}

And serialize by adding the converter to JsonSerializerSettings.Converters:

var settings = new JsonSerializerSettings
{
    Converters = { new ObjectAsObjectArrayConverter<Property>() },
};
var json = JsonConvert.SerializeObject(myClass, Formatting.Indented, settings);

And you will get:

{
  "Properties": [
    {
      "MyFirstProp": "Hello"
    },
    {
      "MySecondProp": "World"
    }
  ]
}

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340
0

to create the output you need, you have to use this code

    var props = new Root {
    Properties = new List<Dictionary<string, string>>
    {
        new Dictionary<string,string> { { "MyFirstProp","Hello"}},
        new Dictionary<string,string> { { "MySecondProp","World"}}
    }};

    var json = JsonConvert.SerializeObject(props, Newtonsoft.Json.Formatting.Indented);

public class Root
{
    public List<Dictionary<string, string>> Properties { get; set; }
}

result

{
  "Properties": [
    {
      "MyFirstProp": "Hello"
    },
    {
      "MySecondProp": "World"
    }
  ]
}
Serge
  • 40,935
  • 4
  • 18
  • 45