How to Serialize Nested Properties to My Class in One Operation with JSON.Net

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:

  1. If [JsonConstructor] is set on a constructor, use that constructor.

  2. Next, in full trust only, when MemberSerialization.Fields is applied, or [Serializable] is applied and DefaultContractResolver.IgnoreSerializableAttribute == false, the special method FormatterServices.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.)

  3. Next, if there is a public parameterless constructor, use it.

  4. Next, if a private parameterless constructor exists and the setting ConstructorHandling.AllowNonPublicDefaultConstructor
    is enabled, the private parameterless constructor is used.

  5. Next, if there is a single public parameterized constructor, use that constructor.

  6. 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



Leave a reply



Submit