sari k
sari k

Reputation: 2111

How to define string to object/dictionary in JSON

I create a class for define my request, I don't get the accepted JSON string

I define this object:

public class Request
{
    public Var_Args[] var_args { get; set; }
}

public class  Var_Args
{
    public object title { get; set; }
    public object owner { get; set; }
}

when I convert it to json, I get the following string:

{"requests":[{"var_args":[{"title":"Test","owner":"skaner"}]}]}

how can I define the class, for get the accepted json string:

{"requests":[{"var_args":[{"title":"Test"},{"owner":"skaner"}]}]}

Upvotes: 3

Views: 297

Answers (3)

Patrick Roberts
Patrick Roberts

Reputation: 51756

You can use System.Reflection to redefine Var_Args as an implementation of the IEnumerable<Dictionary<string,object>> interface by adding two methods to the class:

public class Var_Args : IEnumerable<Dictionary<string,object>>
{
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    public IEnumerator<Dictionary<string,object>> GetEnumerator()
    {
        var Properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

        foreach (var Property in Properties)
        {
            var Entry = new Dictionary<string,object>();
            Entry.Add(Property.Name, Property.GetValue(this));
            yield return Entry;
        }
    }

    public object title { get; set; }
    public object owner { get; set; }
}

While Reflection may be regarded as slow, there is a technique you can use to statically compile an IEnumerable at runtime so that the reflection only occurs once for the definition of the class, like this:

public class Var_Args : IEnumerable<Dictionary<string,object>>
{
    private struct PropertyList<T>
    {
        public static readonly List<Func<T,Dictionary<string,object>>> PropertyGetters;

        static PropertyList()
        {
            PropertyGetters = new List<Func<T,Dictionary<string,object>>>();
            var Properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

            foreach (var Property in Properties)
            {
                var Args = new [] { Expression.Parameter(typeof(T)) };
                var Key = Property.Name;
                var Value = Expression.Property(Args[0], Property);
                Func<T,object> Get = Expression.Lambda<Func<T,object>>(Value, Args).Compile();

                PropertyGetters.Add(obj =>
                {
                    var entry = new Dictionary<string,object>();
                    entry.Add(Key, Get(obj));
                    return entry;
                });
            }
        }
    }

    protected static IEnumerable<Dictionary<string,object>> GetPropertiesAsEntries<T>(T obj)
    {
        return PropertyList<T>.PropertyGetters.Select(f => f(obj));
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    public IEnumerator<Dictionary<string,object>> GetEnumerator()
    {
        return GetPropertiesAsEntries(this).GetEnumerator();
    }

    public object title { get; set; }
    public object owner { get; set; }
}

Upvotes: 0

Marius Bancila
Marius Bancila

Reputation: 16318

You can write a custom JSON converter that can serialize every property of an object (of a known type) into a different JSON object.

public class PropertyAsObjectConverter : JsonConverter
{
   private readonly Type[] _types;

   public PropertyAsObjectConverter(params Type[] types)
   {
      _types = types;
   }

   public override bool CanConvert(Type objectType)
   {
      return _types.Any(t => t == objectType);
   }

   public override bool CanRead
   {
      get { return false; }
   }

   public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
   {
      throw new NotImplementedException();
   }

   public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
   {
      var properties = value.GetType().GetProperties(BindingFlags.Public|BindingFlags.Instance);
      foreach(var property in properties)
      {
         var name = property.Name;
         var attrs = property.GetCustomAttributes(typeof(JsonPropertyAttribute));
         if(attrs != null)
         {
            if (attrs.FirstOrDefault() is JsonPropertyAttribute attr)
               name = attr.PropertyName;
         }

         writer.WriteStartObject();
         writer.WritePropertyName(name);
         serializer.Serialize(writer, property.GetValue(value));
         writer.WriteEndObject();
      }
   }
}

This implements only the serialization, but you can extend it to support deserialization too. You can also extend it to serialize fields should you need that.

You can then define your classes as follows. Notice that I am using JsonPropertyAttribute here to specify the name in the serialized JSON.

public class Content
{
   [JsonProperty("requests")]
   public Request Value { get; set; }
}

public class Request
{
   [JsonProperty("var_args")]
   public VarArgs[] Arguments { get; set; }
}

public class VarArgs
{
   [JsonProperty("title")]
   public object Title { get; set; }

   [JsonProperty("owner")]
   public object Owner { get; set; }
}

This is how you can use it:

static void Main(string[] args)
{
   var request = new Content()
   {
      Value = new Request()
      {
         Arguments = new VarArgs[]
         {
            new VarArgs()
            {
               Title = "Test",
               Owner = "Skaner",
            }
         }
      }
   };

   var text = JsonConvert.SerializeObject(
      request,
      Formatting.None, 
      new PropertyAsObjectConverter(typeof(VarArgs)));

   Console.WriteLine(text);
}

The output for this sample is the one you expect:

{"requests":{"var_args":[{"title":"Test"},{"owner":"Skaner"}]}}

Upvotes: 2

Omar Muscatello
Omar Muscatello

Reputation: 1301

You could use a custom JsonConverter like the below. It takes the Var_Args object and splits it in two different JObject which correspond to two different JSON objects.

public class VarArgsConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var obj = (JObject)JToken.FromObject(value);

        var objTitle = new JObject();
        objTitle.Add("title", obj.GetValue("title"));

        var objOwner = new JObject();
        objOwner.Add("owner", obj.GetValue("owner"));

        objTitle.WriteTo(writer);
        objOwner.WriteTo(writer);
    }


    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
    }

    public override bool CanRead
    {
        get { return false; }
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Var_Args);
    }
}

public class Wrapper
{
    [JsonProperty("requests")]
    public Request Requests { get; set; }
}

public class Request
{
    public Var_Args[] var_args { get; set; }
}

public class Var_Args
{
    public object title { get; set; }
    public object owner { get; set; }
}

Then use it:

var wrapper = new Wrapper();

var request = new Request();
request.var_args = new Var_Args[] {
    new Var_Args(){ title = "Test", owner = "skaner" },
    new Var_Args(){ title = "Test2", owner = "skaner2" }
};

wrapper.Requests = request;

var serialized = JsonConvert.SerializeObject(wrapper, new VarArgsConverter());

Output

{"requests":{"var_args":[{"title":"Test"},{"owner":"skaner"},{"title":"Test2"},{"owner":"skaner2"}]}}

Note: I'm using the Wrapper class just to produce the requested JSON.

If you don't want to specify the converter each time, you can register your converter globally. Please see this answer which explains how you can do that. So, the serializer will use your custom JsonConverter every time you try to serialize a Var_Args object.

If you register the JsonConvert globally you can use:

var serialized = JsonConvert.SerializeObject(wrapper);

Upvotes: 0

Related Questions