Can I serialize nested properties to my class in one operation with Json.net?
You can do it with the following converter:
public class MyModelConverter : JsonConverter
{
[ThreadStatic]
static bool cannotWrite;
// Disables the converter in a thread-safe manner.
bool CannotWrite { get { return cannotWrite; } set { cannotWrite = value; } }
public override bool CanWrite { get { return !CannotWrite; } }
public override bool CanConvert(Type objectType)
{
return typeof(MyModel).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var obj = JObject.Load(reader);
obj.SelectToken("details.size").MoveTo(obj);
obj.SelectToken("details.weight").MoveTo(obj);
using (reader = obj.CreateReader())
{
// Using "populate" avoids infinite recursion.
existingValue = (existingValue ?? new MyModel());
serializer.Populate(reader, existingValue);
}
return existingValue;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// Disabling writing prevents infinite recursion.
using (new PushValue<bool>(true, () => CannotWrite, val => CannotWrite = val))
{
var obj = JObject.FromObject(value, serializer);
var details = new JObject();
obj.Add("details", details);
obj["size"].MoveTo(details);
obj["weight"].MoveTo(details);
obj.WriteTo(writer);
}
}
}
public static class JsonExtensions
{
public static void MoveTo(this JToken token, JObject newParent)
{
if (newParent == null)
throw new ArgumentNullException();
if (token != null)
{
if (token is JProperty)
{
token.Remove();
newParent.Add(token);
}
else if (token.Parent is JProperty)
{
token.Parent.Remove();
newParent.Add(token.Parent);
}
else
{
throw new InvalidOperationException();
}
}
}
}
public struct PushValue<T> : IDisposable
{
Action<T> setValue;
T oldValue;
public PushValue(T value, Func<T> getValue, Action<T> setValue)
{
if (getValue == null || setValue == null)
throw new ArgumentNullException();
this.setValue = setValue;
this.oldValue = getValue();
setValue(value);
}
#region IDisposable Members
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose()
{
if (setValue != null)
setValue(oldValue);
}
#endregion
}
And then use it like this:
[JsonConverter(typeof(MyModelConverter))]
public class MyModel
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("size")]
public string[] Size { get; set; }
[JsonProperty("weight")]
public string Weight { get; set; }
}
public class TestClass
{
public static void Test()
{
string json = @"{
""name"" : ""widget"",
""details"" : {
""size"" : [
""XL"",""M"",""S"",
],
""weight"" : ""heavy""
}
}";
var mod = JsonConvert.DeserializeObject<MyModel>(json);
Debug.WriteLine(JsonConvert.SerializeObject(mod, Formatting.Indented));
}
}
The ReadJson()
method is straightforward: deserialize to a JObject
, restructure the appropriate properties, then populate the MyModel
class. WriteJson
is a little more awkward; the converter needs to temporarily disable itself in a thread-safe manner to generate a "default" JObject
that can be then restructured.
Using Newtonsoft.Json with nested custom classes
Your classes MyClass2
and MyClass3
are read-only. In order for Json.NET to deserialize a read-only type, you must either provide a custom JsonConverter
that manually deserializes and constructs an instance of the type, or provide a parameterized constructor whose argument names match the property names modulo case. You have already created the necessary constructors and so are halfway done.
However your types have parameterless constructors as well. So, which constructor does Json.NET call? For a non-enumerable type that is serialized to a JSON object, the following rules apply:
If
[JsonConstructor]
is set on a constructor, use that constructor.Next, in full trust only, when
MemberSerialization.Fields
is applied, or[Serializable]
is applied andDefaultContractResolver.IgnoreSerializableAttribute == false
, the special methodFormatterServices.GetUninitializedObject()
is used to allocate the object. None of the type's constructors are called.(This is an unusual case that does not arise often.)
Next, if there is a public parameterless constructor, use it.
Next, if a private parameterless constructor exists and the setting
ConstructorHandling.AllowNonPublicDefaultConstructor
is enabled, the private parameterless constructor is used.Next, if there is a single public parameterized constructor, use that constructor.
Failing all of the above, Json.NET cannot construct instances of the type. An exception will get thrown during deserialization unless a custom converter is available.
Thus the parameterless constructors take precedence over the parameterized constructors. To force the parameterized constructors to be used, mark them with [JsonConstructor]
as mentioned above:
public class MyClass3
{
private Regex _myRegex;
private string _myString = null;
public MyClass3() { }
[JsonConstructor]
// The argument names must match the property names modulo case for Json.NET to deserialize the properties successfully.
public MyClass3(string myString, Regex myRegex)
{
_myString = myString;
_myRegex = myRegex;
}
public string MyString { get { return _myString; } }
public Regex MyRegex { get { return _myRegex; } }
}
Alternatively, you could eliminate the parameterless constructor as it apparently did not exist in the first version of your question. Then make the same change to MyClass2
. Now your types will deserialize successfully.
Note that Json.NET has a built-in converter for serializing a Regex
.
Sample fiddle.
How to create a list from nested JSON
Basically this question is not about JSON. It's about linq.
After deserializing your JSON with whatever library you use, you can obtain your golfController
objects using linq and then serialize them.
var root = JsonConvert.DeserializeObject<RootObject>(json);
var controllers = root.operations.Select(o=>o.com_model_golfController).ToList()
var result = JsonConvert.SerializeObject(controllers);
Related Topics
C# Sending Mails with Images Inline Using Smtpclient
Hashtable with Multidimensional Key in C#
How to Ensure a Form Displays on the "Additional" Monitor in a Dual Monitor Scenario
Why Does (Int)(Object)10M Throw "Specified Cast Is Not Valid" Exception
How to Handle Session End in Global.Asax
Could Not Load File or Assembly ... the Parameter Is Incorrect
Pass Parameter to Eventhandler
Should C# Methods That *Can* Be Static Be Static
How to Copy Part of an Array to Another Array in C#
What Is the Algorithm to Convert an Excel Column Letter into Its Number
Linq to SQL and a Running Total on Ordered Results
How to Use Use Late Binding to Get Excel Instance
Will the Dynamic Keyword in C#4 Support Extension Methods
How to Handle JSON That Returns Both a String and a String Array