Home > Software engineering >  JsonConvert List to Json Properties
JsonConvert List to Json Properties

Time:04-15

I would want to convert an object with a list property into a json object. The list property should not be parsed directly, instead the items in the list should be added to the json object as properties. The property names should be custom and have the index as suffix. Only the serialzation is needed.

Example:

class Foo{
   public bool Boolean { get; set; }
   public List<Bar> List { get; set; }
   // 50  additional properties to be serialized.
}

class Bar{
   public string Value{ get; set; }
}

Code:

var bar = new Foo();
bar.Boolean = true;
bar.List = new List<Bar>() { 
   new Bar(){ Value = "Foo1" }, 
   new Bar(){ Value = "Bar2" },
   new Bar(){ Value = "Foo3" } 
};

JsonConvert.Serialize(bar)

Desired output:

{
   "Boolean": true,
   "property1" : "Foo1",
   "property2" : "Bar2",
   "property3" : "Foo3"
}

Note that, in my real classes, there are over 50 other properties for Foo so practically I cannot write a JsonConverter<Foo> that serializes each one manually.

CodePudding user response:

You can generate the JSON you require with a custom JsonConverter. Since your "real" data model has 50 properties, you can use Json.NET's own contract metadata to loop through the properties of Foo and serialize them appropriately. The following does the trick:

public class ItemListContainerConverter<TContainer, TItem> : JsonConverter<TContainer>
{
    protected virtual string PropertyName => "property";

    public override void WriteJson(JsonWriter writer, TContainer value, JsonSerializer serializer)
    {
        var contract = serializer.ContractResolver.ResolveContract(value.GetType()) as JsonObjectContract;
        if (contract == null)
            throw new JsonSerializationException("value is not a JSON object");
        JsonProperty itemListProperty = null;
        writer.WriteStartObject();
        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;
            if (typeof(IEnumerable<TItem>).IsAssignableFrom(property.PropertyType))
            {
                itemListProperty = (itemListProperty == null ? property : throw new JsonSerializationException("Too many IEnumerable<Bar> properties"));
            }
            else
            {
                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);            
            }
        }
        if (itemListProperty != null)
        {
            var itemList = itemListProperty.ValueProvider.GetValue(value) as IEnumerable<TItem>;
            if (itemList != null)
            {
                var itemContract = serializer.ContractResolver.ResolveContract(typeof(TItem));
                var valueMethod = GetItemValueMethod(itemContract);
                int i = 1;
                foreach (var item in itemList)
                {
                    writer.WritePropertyName(PropertyName   (i  ).ToString(CultureInfo.InvariantCulture));
                    serializer.Serialize(writer, valueMethod(item));
                }
            }
        }
        writer.WriteEndObject();
    }

    protected virtual Func<object, object> GetItemValueMethod(JsonContract itemContract)
    {
        if (!(itemContract is JsonObjectContract objectContract))
            throw new JsonSerializationException("item contract is not a JsonObjectContract");
        var property = objectContract.Properties.Single(p => ShouldSerialize(p));
        return (o) => property.ValueProvider.GetValue(o);
    }
    protected virtual bool ShouldSerialize(JsonProperty property) => property.Readable && !property.Ignored;
    protected virtual bool ShouldSerialize(JsonProperty property, object value) => ShouldSerialize(property) && (property.ShouldSerialize == null || property.ShouldSerialize(value));

    public override TContainer ReadJson(JsonReader reader, Type objectType, TContainer existingValue, bool hasExistingValue, JsonSerializer serializer) =>
        // Not requested to be implemnented as per the question.
        throw new NotImplementedException();
}

And then you can serialize your model Foo as follows:

var settings = new JsonSerializerSettings
{
    Converters = { new ItemListContainerConverter<Foo, Bar>() }
};
var json = JsonConvert.SerializeObject(foo, Formatting.Indented, settings);

Which results in

{
  "Boolean": true,
  "property1": "Foo1",
  "property2": "Bar2",
  "property3": "Foo3"
}

Notes:

  • This solution does not require serialization to an intermediate JToken representation.

  • The converter ItemListContainerConverter<TContainer, TItem> assumes that the container type TContainer will be serialized as a JSON object that has a single property of type IEnumerable<TItem> and any number of additional properties. The additional properties will be serialized normally while the items of the IEnumerable<TItem> collection will be serialized as "propertyI" properties as required.

    It assumes that the item type TItem has a single serializable property. If your TItem has more than one property, you can override GetItemValueMethod() to select the property you wish to serialize as the value of the "propertyI" properties.

  • Deserialization was not implemented as it was not requested in the question.

Demo fiddle here.

CodePudding user response:

Here is one other approach to produce expected JSON without JSON converter

Redefine the POCO object as below

public class Foo
{
    public bool Boolean;

    [JsonIgnore]
    public List<Bar> List;

    [JsonExtensionData]
    public Dictionary<string, JToken> Values
    {
        get
        {
            return List.Select((v, i) => new KeyValuePair<string, JToken>($"Property{i   1}", JValue.CreateString(v.Value))).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
        }
    }
}

public class Bar{
   public string Value{ get; set; }
}

Then use JSON library to serialize as below

var bar = new Foo();
bar.Boolean = true;
bar.List = new List<Bar>() { 
   new Bar(){ Value = "Foo1" }, 
   new Bar(){ Value = "Bar2" },
   new Bar(){ Value = "Foo3" } 
};

var json = JsonConvert.SerializeObject(bar, Formatting.Indented);
Console.WriteLine(json);

Sample fiddle: https://dotnetfiddle.net/jgWlLB

CodePudding user response:

Classes:

public class Foo
{
    public bool Boolean;

    [JsonIgnore]
    public List<Bar> List;
}

class Bar{
   string Value{ get; set; }
}

public class FooConverter : JsonConverter<Foo>
{
    public override Foo ReadJson(JsonReader reader, Type objectType, Foo existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, Foo value, JsonSerializer serializer)
    {
        var t = JToken.FromObject(value);

        JObject o = (JObject)t;

        for (int i = 0; i < value.List.Count; i  )
        {
            o.Add($"prop{i   1}", new JValue(value.List[i].Value));
        }
        o.WriteTo(writer);
    }
}

Code:

var bar = new Foo();
bar.Boolean = true;
bar.List = new List<Bar>() { 
   new Bar(){ Value = "Foo1" }, 
   new Bar(){ Value = "Bar2" },
   new Bar(){ Value = "Foo3" } 
};

JsonConvert.SerializeObject(bar, Formatting.Indented, new FooConverter())

My earlier attempts failed because i tried the simple approach of just passing bar to SerialzeObjects and adding the JsonConverter attribute to the Foo class and so it kept looping on itself.

CodePudding user response:

try this

    JObject o = (JObject)JToken.FromObject(bar);
    var i = 0;
    foreach (var item in (JArray)o["List"])
    {
        i  ;
        o.Add("property"   i.ToString(), item["Value"]);
    }
    o.Remove("List");
    o.ToString();

result

{
  "Boolean": true,
  "property1": "Foo1",
  "property2": "Bar2",
  "property3": "Foo3"
}
  • Related