Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Samples for Newtonsoft migration doc #1837

Merged
merged 10 commits into from
Dec 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class DateTimeOffsetNullHandlingConverter : JsonConverter<DateTimeOffset>

{
public override DateTimeOffset Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
return default;
}
return reader.GetDateTimeOffset();
}

public override void Write(
Utf8JsonWriter writer,
DateTimeOffset value,
JsonSerializerOptions options)
{
writer.WriteStringValue(value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace SystemTextJsonSamples
{
public class ConvertInferredTypesToObject
public class DeserializeInferredTypesToObject
{
public static void Run()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class DeserializeNullToNonnullableType
{
public static void Run()
{
string jsonString;
var wf = WeatherForecastFactories.CreateWeatherForecast();

var serializeOptions = new JsonSerializerOptions();
serializeOptions.WriteIndented = true;
serializeOptions.Converters.Add(new DateTimeOffsetNullHandlingConverter());
jsonString = JsonSerializer.Serialize(wf, serializeOptions);
Console.WriteLine($"JSON with valid Date:\n{jsonString}\n");

var deserializeOptions = new JsonSerializerOptions();
tdykstra marked this conversation as resolved.
Show resolved Hide resolved
deserializeOptions.Converters.Add(new DateTimeOffsetNullHandlingConverter());
wf = JsonSerializer.Deserialize<WeatherForecast>(jsonString, deserializeOptions);
wf.DisplayPropertyValues();

jsonString = @"{""Date"": null,""TemperatureCelsius"": 25,""Summary"":""Hot""}";
Console.WriteLine($"JSON with null Date:\n{jsonString}\n");

// The missing-date JSON deserializes with error if the converter isn't used.
tdykstra marked this conversation as resolved.
Show resolved Hide resolved
try
{
wf = JsonSerializer.Deserialize<WeatherForecast>(jsonString);
}
catch (Exception ex)
tdykstra marked this conversation as resolved.
Show resolved Hide resolved
{
Console.WriteLine($"Exception thrown: {ex.Message}\n");
}

Console.WriteLine("Deserialize with converter");
deserializeOptions = new JsonSerializerOptions();
deserializeOptions.Converters.Add(new DateTimeOffsetNullHandlingConverter());
tdykstra marked this conversation as resolved.
Show resolved Hide resolved
wf = JsonSerializer.Deserialize<WeatherForecast>(jsonString, deserializeOptions);
wf.DisplayPropertyValues();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class DeserializeRequiredProperty
{
public static void Run()
{
string jsonString;
var wf = WeatherForecastFactories.CreateWeatherForecast();

var serializeOptions = new JsonSerializerOptions();
serializeOptions.WriteIndented = true;
serializeOptions.Converters.Add(new WeatherForecastRequiredPropertyConverter());
jsonString = JsonSerializer.Serialize(wf, serializeOptions);
Console.WriteLine($"JSON with Date:\n{jsonString}\n");

var deserializeOptions = new JsonSerializerOptions();
deserializeOptions.Converters.Add(new WeatherForecastRequiredPropertyConverter());
wf = JsonSerializer.Deserialize<WeatherForecast>(jsonString, deserializeOptions);
wf.DisplayPropertyValues();

jsonString = @"{""TemperatureCelsius"": 25,""Summary"":""Hot""}";
Console.WriteLine($"JSON without Date:\n{jsonString}\n");

// The missing-date JSON deserializes without error if the converter isn't used.
Console.WriteLine("Deserialize without converter");
wf = JsonSerializer.Deserialize<WeatherForecast>(jsonString);
wf.DisplayPropertyValues();

Console.WriteLine("Deserialize with converter");
try
{
deserializeOptions = new JsonSerializerOptions();
deserializeOptions.Converters.Add(new WeatherForecastRequiredPropertyConverter());
wf = JsonSerializer.Deserialize<WeatherForecast>(jsonString, deserializeOptions);
}
catch (Exception ex)
tdykstra marked this conversation as resolved.
Show resolved Hide resolved
{
Console.WriteLine($"Exception thrown: {ex.Message}\n");
}
// wf object is unchanged if exception is thrown.
wf.DisplayPropertyValues();
}
}

}
19 changes: 19 additions & 0 deletions snippets/core/system-text-json/csharp/ImmutablePoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace SystemTextJsonSamples
{
// <SnippetImmutablePoint>
tdykstra marked this conversation as resolved.
Show resolved Hide resolved
public struct ImmutablePoint
{
private readonly int _x;
private readonly int _y;

public ImmutablePoint(int x, int y)
{
_x = x;
_y = y;
}

public int X { get { return _x; } }
tdykstra marked this conversation as resolved.
Show resolved Hide resolved
public int Y { get { return _y; } }
}
// </SnippetImmutablePoint>
}
112 changes: 112 additions & 0 deletions snippets/core/system-text-json/csharp/ImmutablePointConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class ImmutablePointConverter : JsonConverter<ImmutablePoint>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this is the right place for this, but this converter can be written more efficiently. So, maybe we suggest that?

  1. reader.GetString() for property name matching isn't necessary. You could use ValueTextEquals
  2. Comparison with JsonEncodedText is faster than comparison with string.
  3. Rather than asking for an int converter every time, it's probably better to get it once and either pass it in, or store it as a field by getting it in the custom ctor. cc @steveharter, @layomia
  4. You can remove one of the checks at the end (if (!xSet || !ySet)) by crafting the conditions differently.
public class ImmutablePointNewConverter : JsonConverter<ImmutablePoint>
{
    private readonly JsonEncodedText XName = JsonEncodedText.Encode("X");
    private readonly JsonEncodedText YName = JsonEncodedText.Encode("Y");

    private readonly JsonConverter<int> _intConverter;

    public ImmutablePointNewConverter(JsonSerializerOptions options)
    {
        if (options?.GetConverter(typeof(int)) is JsonConverter<int> intConverter)
        {
            _intConverter = intConverter;
        }
        else
        {
            throw new InvalidOperationException();
        }
    }

    public override ImmutablePoint Read(
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options)
    {
        if (reader.TokenType != JsonTokenType.StartObject)
        {
            throw new JsonException();
        };

        int x = default;
        bool xSet = false;

        int y = default;
        bool ySet = false;

        // Get the first property.
        reader.Read();
        if (reader.TokenType != JsonTokenType.PropertyName)
        {
            throw new JsonException();
        }

        if (reader.ValueTextEquals(XName.EncodedUtf8Bytes))
        {
            x = ReadProperty(ref reader, options);
            xSet = true;
        }
        else if (reader.ValueTextEquals(YName.EncodedUtf8Bytes))
        {
            y = ReadProperty(ref reader, options);
            ySet = true;
        }
        else
        {
            throw new JsonException();
        }

        // Get the second property.
        reader.Read();
        if (reader.TokenType != JsonTokenType.PropertyName)
        {
            throw new JsonException();
        }

        if (xSet && reader.ValueTextEquals(YName.EncodedUtf8Bytes))
        {
            y = ReadProperty(ref reader, options);
        }
        else if (ySet && reader.ValueTextEquals(XName.EncodedUtf8Bytes))
        {
            x = ReadProperty(ref reader, options);
        }
        else
        {
            throw new JsonException();
        }

        reader.Read();

        if (reader.TokenType != JsonTokenType.EndObject)
        {
            throw new JsonException();
        }

        return new ImmutablePoint(x, y);
    }

    private int ReadProperty(ref Utf8JsonReader reader, JsonSerializerOptions options)
    {
        Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);

        reader.Read();
        return _intConverter.Read(ref reader, typeof(int), options);
    }

    public override void Write(
        Utf8JsonWriter writer,
        ImmutablePoint value,
        JsonSerializerOptions options)
    {
        // Don't pass in options when recursively calling Serialize.
        JsonSerializer.Serialize(writer, value);
    }
}


{
private const string XName = "X";
private const string YName = "Y";

public override ImmutablePoint Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
};

int x = default;
bool xSet = false;

int y = default;
bool ySet = false;

// Get the first property.
reader.Read();
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}

string propertyName = reader.GetString();
if (propertyName == XName)
{
x = ReadProperty(ref reader, options);
xSet = true;
}
else if (propertyName == YName)
{
y = ReadProperty(ref reader, options);
ySet = true;
}
else
{
throw new JsonException();
}

// Get the second property.
reader.Read();
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}

propertyName = reader.GetString();
if (propertyName == XName)
{
x = ReadProperty(ref reader, options);
xSet = true;
}
else if (propertyName == YName)
{
y = ReadProperty(ref reader, options);
ySet = true;
}
else
{
throw new JsonException();
}
tdykstra marked this conversation as resolved.
Show resolved Hide resolved

if (!xSet || !ySet)
{
throw new JsonException();
}

reader.Read();

if (reader.TokenType != JsonTokenType.EndObject)
{
throw new JsonException();
}

return new ImmutablePoint(x, y);
}

public int ReadProperty(ref Utf8JsonReader reader, JsonSerializerOptions options)
tdykstra marked this conversation as resolved.
Show resolved Hide resolved
{
if (options?.GetConverter(typeof(int)) is JsonConverter<int> intConverter)
{
reader.Read();
return intConverter.Read(ref reader, typeof(int), options);
}
else
{
throw new JsonException();
}
}

public override void Write(
Utf8JsonWriter writer,
ImmutablePoint value,
JsonSerializerOptions options)
{
// Don't pass in options when recursively calling Serialize.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we state why? cc @steveharter

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it would cause a stackoverflow in this case. I don't know if this is a bug or expected.

https://github.com/dotnet/runtime/blob/f77914d4c9045a01b3a91b63c28805f24850c274/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs#L89

System.Text.Json.Tests.dll!System.Text.Json.Serialization.Tests.ReadValueTests.ImmutablePointConverter.Write(System.Text.Json.Utf8JsonWriter writer, System.Text.Json.Serialization.Tests.ReadValueTests.ImmutablePoint value, System.Text.Json.JsonSerializerOptions options) Line 1611 C#
System.Text.Json.dll!System.Text.Json.JsonPropertyInfoNotNullable<object, System.Text.Json.Serialization.Tests.ReadValueTests.ImmutablePoint, System.Text.Json.Serialization.Tests.ReadValueTests.ImmutablePoint>.OnWrite(ref System.Text.Json.WriteStackFrame current, System.Text.Json.Utf8JsonWriter writer) Line 91 C#
System.Text.Json.dll!System.Text.Json.JsonPropertyInfo.Write(ref System.Text.Json.WriteStack state, System.Text.Json.Utf8JsonWriter writer) Line 571 C#
System.Text.Json.dll!System.Text.Json.JsonSerializer.Write(System.Text.Json.Utf8JsonWriter writer, int originalWriterDepth, int flushThreshold, System.Text.Json.JsonSerializerOptions options, ref System.Text.Json.WriteStack state) Line 37 C#
System.Text.Json.dll!System.Text.Json.JsonSerializer.WriteCore(System.Text.Json.Utf8JsonWriter writer, object value, System.Type type, System.Text.Json.JsonSerializerOptions options) Line 139 C#
System.Text.Json.dll!System.Text.Json.JsonSerializer.WriteValueCore(System.Text.Json.Utf8JsonWriter writer, object value, System.Type type, System.Text.Json.JsonSerializerOptions options) Line 107 C#
System.Text.Json.dll!System.Text.Json.JsonSerializer.Serialize<System.Text.Json.Serialization.Tests.ReadValueTests.ImmutablePoint>(System.Text.Json.Utf8JsonWriter writer, System.Text.Json.Serialization.Tests.ReadValueTests.ImmutablePoint value, System.Text.Json.JsonSerializerOptions options) Line 22 C#

In the key value pair case, looks like we only call it for object and end up calling GetType on it the next time, so it doesn't usually stackoverflow.

But, this sample breaks that:

var kvp = new KeyValuePair<object, int>("foo", 0);
KeyValuePair<object, int> parent = default;

for (int i = 0; i < 1_000; i++)
{
    parent = new KeyValuePair<object, int>(kvp, i);
    kvp = parent;
}

Console.WriteLine(JsonSerializer.Serialize(parent));

Copy link
Contributor Author

@tdykstra tdykstra Dec 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[EDIT] The bug here is caused by my mistake in adapting the KeyValueConverter code. I have corrected it now to call the int converter for each property instead of calling into Serialize(). So the advice to not pass in options doesn't apply in this case.

public override void Write(
    Utf8JsonWriter writer,
    ImmutablePoint value,
    JsonSerializerOptions options)
{
    writer.WriteStartObject();
    WriteProperty(writer, value.X, XName, options);
    WriteProperty(writer, value.Y, YName, options);
    writer.WriteEndObject();
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JsonSerializer.Serialize(writer, value);
}
}
}
31 changes: 31 additions & 0 deletions snippets/core/system-text-json/csharp/LongToStringConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Buffers;
using System.Buffers.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class LongToStringConverter : JsonConverter<long>
{
public override long Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
ReadOnlySpan<byte> span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
if (Utf8Parser.TryParse(span, out long number, out int bytesConsumed) && span.Length == bytesConsumed)
return number;

if (Int64.TryParse(reader.GetString(), out number))
return number;
}

return reader.GetInt64();
}

public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
}
24 changes: 21 additions & 3 deletions snippets/core/system-text-json/csharp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,31 @@ static async Task Main(string[] args)
RegisterConverterWithAttributeOnType.Run();

Console.WriteLine("\n============================= Custom converter Dictionary with TKey = Enum\n");
ConvertDictionaryTkeyEnumTValue.Run();
RoundtripDictionaryTkeyEnumTValue.Run();

Console.WriteLine("\n============================= Custom converter Polymorphic\n");
ConvertPolymorphic.Run();
RoundtripPolymorphic.Run();

Console.WriteLine("\n============================= Custom converter inferred types to Object\n");
ConvertInferredTypesToObject.Run();
DeserializeInferredTypesToObject.Run();

Console.WriteLine("\n============================= Custom converter long to string\n");
RoundtripLongToString.Run();

Console.WriteLine("\n============================= Callbacks\n");
RoundtripCallbacks.Run();

Console.WriteLine("\n============================= Required property\n");
DeserializeRequiredProperty.Run();

Console.WriteLine("\n============================= Null value to nonnullable type\n");
DeserializeNullToNonnullableType.Run();

Console.WriteLine("\n============================= Immutable struct\n");
RoundtripImmutableStruct.Run();

Console.WriteLine("\n============================= Runtime property exclusion\n");
SerializeRuntimePropertyExclusion.Run();

Console.WriteLine("\n============================= JsonDocument data access\n");
JsonDocumentDataAccess.Run();
Expand Down
36 changes: 36 additions & 0 deletions snippets/core/system-text-json/csharp/RoundtripCallbacks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class RoundtripCallbacks
{
public static void Run()
{
string jsonString;
var wf = WeatherForecastFactories.CreateWeatherForecast();
wf.DisplayPropertyValues();

// <SnippetSerialize>
var serializeOptions = new JsonSerializerOptions();
serializeOptions.Converters.Add(new WeatherForecastCallbacksConverter());
serializeOptions.WriteIndented = true;
jsonString = JsonSerializer.Serialize(wf, serializeOptions);
// </SnippetSerialize>

Console.WriteLine($"JSON output:\n{jsonString}\n");
//jsonString = @"{""Date"": null,""TemperatureCelsius"": 25,""Summary"":""Hot""}";
tdykstra marked this conversation as resolved.
Show resolved Hide resolved

// <SnippetDeserialize>
var deserializeOptions = new JsonSerializerOptions();
deserializeOptions.Converters.Add(new WeatherForecastCallbacksConverter());
wf = JsonSerializer.Deserialize<WeatherForecast>(jsonString, deserializeOptions);
// <SnippetDeserialize>

wf.DisplayPropertyValues();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace SystemTextJsonSamples
{
public class ConvertDictionaryTkeyEnumTValue
public class RoundtripDictionaryTkeyEnumTValue
{
public static void Run()
{
Expand Down
Loading