JSON Serializer Object with Internal Properties

JSON Serializer object with internal properties

Mark the internal properties to be serialized with the [JsonProperty] attribute:

public class Foo
{
[JsonProperty]
internal int num1 { get; set; }
[JsonProperty]
internal double num2 { get; set; }

public string Description { get; set; }

public override string ToString()
{
if (!string.IsNullOrEmpty(Description))
return Description;

return base.ToString();
}
}

And then later, to test:

Foo f = new Foo();
f.Description = "Foo Example";
f.num1 = 101;
f.num2 = 202;
JsonSerializerSettings settings = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All };

var jsonOutput = JsonConvert.SerializeObject(f, Formatting.Indented, settings);

Console.WriteLine(jsonOutput);

I get the following output:

{
"$type": "Tile.JsonInternalPropertySerialization.Foo, Tile",
"num1": 101,
"num2": 202.0,
"Description": "Foo Example"
}

(Where "Tile.JsonInternalPropertySerialization" and "Tile" are namespace and assembly names I am using).

As an aside, when using TypeNameHandling, do take note of this caution from the Newtonsoft docs:

TypeNameHandling should be used with caution when your application deserializes JSON from an external source. Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None.

For a discussion of why this may be necessary, see TypeNameHandling caution in Newtonsoft Json and and External json vulnerable because of Json.Net TypeNameHandling auto?.

Custom JSON serializer for optional property with System.Text.Json

A custom JsonConverter<T> cannot prevent the serialization of a value to which the converter applies, see [System.Text.Json] Converter-level conditional serialization #36275 for confirmation.

In .Net 5 there is an option to ignore default values which should do what you need, see How to ignore properties with System.Text.Json. This version introduces JsonIgnoreCondition.WhenWritingDefault:

public enum JsonIgnoreCondition
{
// Property is never ignored during serialization or deserialization.
Never = 0,
// Property is always ignored during serialization and deserialization.
Always = 1,
// If the value is the default, the property is ignored during serialization.
// This is applied to both reference and value-type properties and fields.
WhenWritingDefault = 2,
// If the value is null, the property is ignored during serialization.
// This is applied only to reference-type properties and fields.
WhenWritingNull = 3,
}

You will be able to apply the condition to specific properties via [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] or globally by setting JsonSerializerOptions.DefaultIgnoreCondition.

Thus in .Net 5 your class would look like:

public class CustomType
{
[JsonPropertyName("foo")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public Optional<int?> Foo { get; set; }

[JsonPropertyName("bar")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public Optional<int?> Bar { get; set; }

[JsonPropertyName("baz")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public Optional<int?> Baz { get; set; }
}

And the HasValue check should be removed from OptionalConverterInner<T>.Write():

public override void Write(Utf8JsonWriter writer, Optional<T> value, JsonSerializerOptions options) =>
JsonSerializer.Serialize(writer, value.Value, options);

Demo fiddle #1 here.

In .Net 3, as there is no conditional serialization mechanism in System.Text.Json, your only option to conditionally omit optional properties without a value is to write a custom JsonConverter<T> for all classes that contain optional properties. This is not made easy by the fact that JsonSerializer does not provide any access to its internal contract information so we need to either handcraft a converter for each and every such type, or write our own generic code via reflection.

Here is one attempt to create such generic code:

public interface IHasValue
{
bool HasValue { get; }
object GetValue();
}

public readonly struct Optional<T> : IHasValue
{
public Optional(T value)
{
this.HasValue = true;
this.Value = value;
}

public bool HasValue { get; }
public T Value { get; }
public object GetValue() => Value;
public static implicit operator Optional<T>(T value) => new Optional<T>(value);
public override string ToString() => this.HasValue ? (this.Value?.ToString() ?? "null") : "unspecified";
}

public class TypeWithOptionalsConverter<T> : JsonConverter<T> where T : class, new()
{
class TypeWithOptionalsConverterContractFactory : JsonObjectContractFactory<T>
{
protected override Expression CreateSetterCastExpression(Expression e, Type t)
{
// (Optional<Nullable<T>>)(object)default(T) does not work, even though (Optional<Nullable<T>>)default(T) does work.
// To avoid the problem we need to first cast to Nullable<T>, then to Optional<Nullable<T>>
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Optional<>))
return Expression.Convert(Expression.Convert(e, t.GetGenericArguments()[0]), t);
return base.CreateSetterCastExpression(e, t);
}
}

static readonly TypeWithOptionalsConverterContractFactory contractFactory = new TypeWithOptionalsConverterContractFactory();

public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var properties = contractFactory.GetProperties(typeToConvert);

if (reader.TokenType == JsonTokenType.Null)
return null;
if (reader.TokenType != JsonTokenType.StartObject)
throw new JsonException();
var value = new T();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
return value;
if (reader.TokenType != JsonTokenType.PropertyName)
throw new JsonException();
string propertyName = reader.GetString();
if (!properties.TryGetValue(propertyName, out var property) || property.SetValue == null)
{
reader.Skip();
}
else
{
var type = property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Optional<>)
? property.PropertyType.GetGenericArguments()[0] : property.PropertyType;
var item = JsonSerializer.Deserialize(ref reader, type, options);
property.SetValue(value, item);
}
}
throw new JsonException();
}

public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
writer.WriteStartObject();
foreach (var property in contractFactory.GetProperties(value.GetType()))
{
if (options.IgnoreReadOnlyProperties && property.Value.SetValue == null)
continue;
var item = property.Value.GetValue(value);
if (item is IHasValue hasValue)
{
if (!hasValue.HasValue)
continue;
writer.WritePropertyName(property.Key);
JsonSerializer.Serialize(writer, hasValue.GetValue(), options);
}
else
{
if (options.IgnoreNullValues && item == null)
continue;
writer.WritePropertyName(property.Key);
JsonSerializer.Serialize(writer, item, property.Value.PropertyType, options);
}
}
writer.WriteEndObject();
}
}

public class JsonPropertyContract<TBase>
{
internal JsonPropertyContract(PropertyInfo property, Func<Expression, Type, Expression> setterCastExpression)
{
this.GetValue = ExpressionExtensions.GetPropertyFunc<TBase>(property).Compile();
if (property.GetSetMethod() != null)
this.SetValue = ExpressionExtensions.SetPropertyFunc<TBase>(property, setterCastExpression).Compile();
this.PropertyType = property.PropertyType;
}
public Func<TBase, object> GetValue { get; }
public Action<TBase, object> SetValue { get; }
public Type PropertyType { get; }
}

public class JsonObjectContractFactory<TBase>
{
protected virtual Expression CreateSetterCastExpression(Expression e, Type t) => Expression.Convert(e, t);

ConcurrentDictionary<Type, ReadOnlyDictionary<string, JsonPropertyContract<TBase>>> Properties { get; } =
new ConcurrentDictionary<Type, ReadOnlyDictionary<string, JsonPropertyContract<TBase>>>();

ReadOnlyDictionary<string, JsonPropertyContract<TBase>> CreateProperties(Type type)
{
if (!typeof(TBase).IsAssignableFrom(type))
throw new ArgumentException();
var dictionary = type
.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
.Where(p => p.GetIndexParameters().Length == 0 && p.GetGetMethod() != null
&& !Attribute.IsDefined(p, typeof(System.Text.Json.Serialization.JsonIgnoreAttribute)))
.ToDictionary(p => p.GetCustomAttribute<System.Text.Json.Serialization.JsonPropertyNameAttribute>()?.Name ?? p.Name,
p => new JsonPropertyContract<TBase>(p, (e, t) => CreateSetterCastExpression(e, t)),
StringComparer.OrdinalIgnoreCase);
return dictionary.ToReadOnly();
}

public IReadOnlyDictionary<string, JsonPropertyContract<TBase>> GetProperties(Type type) => Properties.GetOrAdd(type, t => CreateProperties(t));
}

public static class DictionaryExtensions
{
public static ReadOnlyDictionary<TKey, TValue> ToReadOnly<TKey, TValue>(this IDictionary<TKey, TValue> dictionary) =>
new ReadOnlyDictionary<TKey, TValue>(dictionary ?? throw new ArgumentNullException());
}

public static class ExpressionExtensions
{
public static Expression<Func<T, object>> GetPropertyFunc<T>(PropertyInfo property)
{
// (x) => (object)x.Property;
var arg = Expression.Parameter(typeof(T), "x");
var getter = Expression.Property(arg, property);
var cast = Expression.Convert(getter, typeof(object));
return Expression.Lambda<Func<T, object>>(cast, arg);
}

public static Expression<Action<T, object>> SetPropertyFunc<T>(PropertyInfo property, Func<Expression, Type, Expression> setterCastExpression)
{
//(x, y) => x.Property = (TProperty)y
var arg1 = Expression.Parameter(typeof(T), "x");
var arg2 = Expression.Parameter(typeof(object), "y");
var cast = setterCastExpression(arg2, property.PropertyType);
var setter = Expression.Call(arg1, property.GetSetMethod(), cast);
return Expression.Lambda<Action<T, object>>(setter, arg1, arg2);
}
}

Notes:

  • CustomType remains as shown in your question.

  • No attempt was made to handle the presence of a naming policy in JsonSerializerOptions.PropertyNamingPolicy. You could implement this in TypeWithOptionalsConverter<T> if necessary.

  • I added a non-generic interface IHasValue to enable easier access to a boxed Optional<T> during serialization.

Demo fiddle #2 here.

Alternatively, you could stick with Json.NET which supports this at the property and contact level. See:

  • Optionally serialize a property based on its runtime value (essentially a duplicate of your question).

  • how to dynamic jsonignore according to user authorize?

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

How to serialize an object to a JSON string property instead of an object using Json.Net

Use a custom JsonConverter and you can control your conversion to output whatever you want.

Something like:

    public class BarConverter : JsonConverter
{

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

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var bar = value as Bar;
serializer.Serialize(writer, bar.Name);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Note: if you need to read to, you'll need to implement that here too
// otherwise just throw a NotImplementException and override `CanRead` to return false
throw new NotImplementedException();
}
}

Then you can either decorate your property or the Bar class (depending on whether you always want Bar serialized like this, or only for this property) with the JsonConverterAttribute:

[JsonConverter(typeof(BarConverter))]
public Bar Bar { get; set; }

Or:

[JsonConverter(typeof(BarConverter))]
public class Bar

Another "quick and dirty" way to do it is to just have a shadow property that will be serialized:

public class Foo
{
[JsonProperty("bar")] // this will be serialized as "bar"
public string BarName
{
get { return Bar.Name; }
}

[JsonIgnore] // this won't be serialized
public Bar Bar { get; set; }
}

Note if you want to be able to read then you'd need to provide a setter too and figure out how to convert the string name back to an instance of Bar. That's where the quick and dirty solution gets a little unpleasant because you don't have a easy way to restrict setting BarName to just during deserialization.

How to deserialize a property on JSON from one type but serialize to another type with the same name?

Thanks to @Fildor for leading me in the right direction.
The answer was deceptively simple, add a type converter and decorate the new property with the converter.

public class Something {
[JsonConverter(typeof(FeeConverter))]
public Fee Fee { get; set;}
}

public class Fee {
public decimal? Amount { get; set; }
public int Type { get; set; }
}

public class FeeConverter : JsonConverter
{

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Float || reader.TokenType == JsonToken.Integer)
{
var property = JValue.Load(reader);
return new Fee() { Amount = property.Value<decimal>() };
}

return serializer.Deserialize(reader, objectType);
}

public override bool CanWrite => false;

public override bool CanConvert(Type objectType) => false;
}

Get .NET Core JsonSerializer to serialize private members

It seems System.Text.Json does not support private property serialization.

https://learn.microsoft.com/tr-tr/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to#internal-and-private-property-setters-and-getters

But as the Microsoft's document says, you can do it with custom converters.

https://www.thinktecture.com/en/asp-net/aspnet-core-3-0-custom-jsonconverter-for-the-new-system_text_json/

Code snippet for serialization & deserialization;

  public class Category
{
public Category(List<string> names)
{
this.Names1 = names;
}

private List<string> Names1 { get; set; }
public string Name2 { get; set; }
public string Name3 { get; set; }
}

public class CategoryJsonConverter : JsonConverter<Category>
{
public override Category Read(ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
var name = reader.GetString();

var source = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(name);

var category = new Category(null);

var categoryType = category.GetType();
var categoryProps = categoryType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

foreach (var s in source.Keys)
{
var categoryProp = categoryProps.FirstOrDefault(x => x.Name == s);

if (categoryProp != null)
{
var value = JsonSerializer.Deserialize(source[s].GetRawText(), categoryProp.PropertyType);

categoryType.InvokeMember(categoryProp.Name,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance,
null,
category,
new object[] { value });
}
}

return category;
}

public override void Write(Utf8JsonWriter writer,
Category value,
JsonSerializerOptions options)
{
var props = value.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.ToDictionary(x => x.Name, x => x.GetValue(value));

var ser = JsonSerializer.Serialize(props);

writer.WriteStringValue(ser);
}
}

static void Main(string[] args)
{
Category category = new Category(new List<string>() { "1" });
category.Name2 = "2";
category.Name3 = "3";

var opt = new JsonSerializerOptions
{
Converters = { new CategoryJsonConverter() },
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};

var json = JsonSerializer.Serialize(category, opt);

var obj = JsonSerializer.Deserialize<Category>(json, opt);

Console.WriteLine(json);
Console.ReadKey();
}

Result;

"{\"Names1\":[\"1\"],\"Name2\":\"2\",\"Name3\":\"3\"}"

Json.NET Serialize Object Properties Without Parent

You can try to serialize ApiResponseData.User instead of serializing ApiResponseData itself. This way it will only serialize the values of
ApiResponseData.User. Like:

 Newtonsoft.Json.JsonConvert.SerializeObject(ApiResponseData.User);


Related Topics



Leave a reply



Submit