Private Setters in JSON.Net

Private setters in Json.Net


Updated, new answer

I've written a source distribution NuGet for this, that installs a single file with two custom contract resolvers:

  • PrivateSetterContractResolver
  • PrivateSetterCamelCasePropertyNamesContractResolver

Install the NuGet package:

Install-Package JsonNet.ContractResolvers

Then just use any of the resolvers:

var settings = new JsonSerializerSettings
{
ContractResolver = new PrivateSetterContractResolver()
};

var model = JsonConvert.DeserializeObject<Model>(json, settings);

You can read about it here: http://danielwertheim.se/json-net-private-setters-nuget/

GitHub repo: https://github.com/danielwertheim/jsonnet-privatesetterscontractresolvers

Old answer (still valid)

There are two alternatives that can solve the problem.

Alt 1: On the deserializers

ContractResolver.DefaultMembersSearchFlags =
DefaultMembersSearchFlags | BindingFlags.NonPublic;

The default serialization option supports all types of class member. Therefore this solution will return all private members types including fields. I'm only interested in also supporting private setters.

Alt2: Create a custom ContractResolver:

Therefore this is the better options since we just check the properties.

public class SisoJsonDefaultContractResolver : DefaultContractResolver 
{
protected override JsonProperty CreateProperty(
MemberInfo member,
MemberSerialization memberSerialization)
{
//TODO: Maybe cache
var prop = base.CreateProperty(member, memberSerialization);

if (!prop.Writable)
{
var property = member as PropertyInfo;
if (property != null)
{
var hasPrivateSetter = property.GetSetMethod(true) != null;
prop.Writable = hasPrivateSetter;
}
}

return prop;
}
}

For more information, read my post: http://danielwertheim.se/json-net-private-setters/

Deserializing public property with non-public setter in json.net

Yes, you can use a custom ContractResolver to make the internal property writable to Json.Net. Here is the code you would need:

class CustomResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty prop = base.CreateProperty(member, memberSerialization);

if (member.DeclaringType == typeof(A) && prop.PropertyName == "P1")
{
prop.Writable = true;
}

return prop;
}
}

To use the resolver, create an instance of JsonSerializerSettings and set its ContractResolver property to a new instance of the custom resolver. Then, pass the settings to JsonConvert.DeserializeObject<T>().

Demo:

class Program
{
static void Main(string[] args)
{
string json = @"{ ""P1"" : ""42"" }";

JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new CustomResolver();

A a = JsonConvert.DeserializeObject<A>(json, settings);

Console.WriteLine(a.P1);
}
}

Output:

42

Fiddle: https://dotnetfiddle.net/1fw2lC

private set in deserialize json doens't set correctly

Your UserScope only have one constructor which accepts certain arguments (no default constructor with no arguments), and no properties are marked with JsonProperty, so JSON.NET assumes it needs to call that constructor to create an instance. It then matches keys from json string with names of arguments of that constructor. testCenterIds and roleIds names do match said keys, while other argument names do not, so JSON.NET pass nulls there instead. If you rename constructor arguments like this:

public UserScope(List<Guid> testCenterIds, List<Guid> roleIds, List<int> townCodes, List<int> cityCodes, List<int> provinceCodes, List<string> provinceCodes2)
{
this.testCenterIds = testCenterIds;
this.roleIds = roleIds;
this.townCodes = townCodes;
this.CityCodes = cityCodes;
this.provinceCodes = provinceCodes;
this.provinceCodes2 = provinceCodes2;
}

Then they all will be populated. Alternatively you can mark all properties with JsonProperty attribute (good to always do that if possible and suitable) as mentioned in answer linked in comments.

Tagging private setters with JsonIgnoreAttribute

The fact that Json.Net accesses private members when tagged with the [JsonProperty] attribute is actually by design. This feature exists to enable Json.Net to be used for serializing/deserializing the internal state of an object (e.g. to persist it) without requiring exposing a public interface for that state.

From the documentation:

JsonPropertyAttribute

JsonPropertyAttribute has a number of uses:

  • By default the JSON property will have the same name as the .NET property. This attribute allows the name to be customized.

  • Indicates that a property should be serialized when member serialization is set to opt-in.

  • Includes non-public properties in serialization and deserialization.

  • Customize type name, reference, null and default value handling for the property value.

  • Customize the property's collection items JsonConverter, type name handing and reference handling.

Unfortunately, the parameters of the [JsonProperty] attribute apparently do not provide a way to opt out of including non-public members while still allowing you to use other features such as changing the property name. Therefore, you will have to use a workaround in this situation.

You already mentioned a couple of possibilities, to which I will add a third:

  1. Remove the [JsonPropertyAttribute]
  2. Remove the setter entirely and introduce a backing field
  3. Implement a custom JsonConverter for your class

Of these, the second one is the cleanest and easiest to implement while still achieving the result you are looking for: the public property will be serialized using the customized property name, while the private backing field will not be affected during deserialization.

The first option, removing the attribute, won't quite get you what you want. While it will prevent the field from being deserialized into, you will have to settle for the original property name being written to the JSON during serialization.

The third option, writing a converter, gives you complete control over how your class is serialized and deserialized. You can change the property names, omit properties, include additional information that isn't even in the class, whatever. They are not that difficult to write; however, if all you really need is a simple backing field to solve your problem, a converter is probably overkill here. That said, if you're really interested in this option, I'd be happy to provide a simple example. Just let me know.

Edit

Regarding your Extension Data problem-- you're right, that does look like a bug. When I originally tried to reproduce the problem, I could not, because my version of your JsonObject did not contain a default constructor. I was assuming your constructor had a parameter to accept the initial value of the read-only property. Once I removed the parameter, it started misbehaving. I'm not sure why the constructor would make a difference in how the JsonExtensionData is interpretted. However, this does suggest an odd workaround: try adding the following to your class and see if that fixes the problem.

    [JsonConstructor]
private JsonObject(string dummy) : this()
{
}

Use System.Text.Json to deserialize properties with private setters

In .NET Core 3.x, that is not possible. In .NET 5 timeline, support has been added.

Best way to only allow the setting of a property while deserializing a JsonProperty?

If you can use init only properties (since C#9.0) (https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/init):

[JsonProperty(PropertyName = "id")]
public string Id { get; init; }

If not...

private string _id;
[JsonProperty(PropertyName = "id")]
public string Id
{
get { return _id; }
set { _id ??= value; }
}

Unrelated but helpful link on setting default property values if they are not found in the json: Default value for missing properties with JSON.net

JSON deserialise to an object with a private setter

You can use the DataContractJsonSerializer instead of the JavaScriptSerializer. You will need to decorate your class with some data contract attributes.

    [DataContractAttribute()]
public class Message
{
public Message()
{
ID = Guid.NewGuid();
TimeCreated = DateTime.Now;
}

[DataMemberAttribute()]
Guid ID { get; private set; }

[DataMemberAttribute()]
DateTime TimeCreated { get; private set; }

[DataMemberAttribute()]
String Content {get; set;}
}

Generic serialization helper methods

public static string ToJsonString(object obj)
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
using (MemoryStream ms = new MemoryStream())
{
serializer.WriteObject(ms, obj);
StringBuilder sb = new StringBuilder();
sb.Append(Encoding.UTF8.GetString(ms.ToArray()));
return sb.ToString();
}
}

public static T ToObjectFromJson<T>(string json)
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));

using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json)))
{
return (T) serializer.ReadObject(ms);
}
}

In your case, you can deserialize using:

Message msg = ToObjectFromJson<Message>(jsonString);

Why cant I serialize a property that has a private setter (C#, json)

When you use System.Runtime.Serialization without any annotations it will ignore properties that can't be set and serialize all public members of given type that can be serialized and deserialized.

However, if you go for explicit declaration of what it should serialize, it will work:

[DataContract]
public class TestSubObject
{
[DataMember]
public string Property { get; private set; } = "Bar";
}

[DataContract]
public class TestObject
{
[DataMember]
public TestSubObject Sub { get; set; }
[DataMember]
public string Property { get; set; }
}

Output:

{
"Property": "Foo",
"Sub": {
"Property": "Bar"
}
}

Json.net serialize specific private field

There is no need to implement a custom DefaultContractResolver. The solution is to put [JsonProperty] on _hexes and [JsonIgnore] on all the other properties and fields.



Related Topics



Leave a reply



Submit