Search code examples
c#json.net-corenunitsystem.text.json

System.Text.Json.JsonReaderException isn't found in namespace


I'm converting a .NET Framework 4.5 project to .NET Core 3.1. My tests used to use Newtonsoft.Json to check if json is valid and now I would want to implement the same with the built-in System.Text.Json. It appears that

JsonElement values = JsonDocument.Parse(json).RootElement;

throws System.Text.Json.JsonReaderException, but I'm unable to catch this as pointing to this exception results in error

The type or namespace name 'JsonReaderException' does not exist in the namespace 'System.Text.Json' (are you missing an assembly reference?)

I would just want to understand how is it possible that something that doesn't actually seem to exist can be thrown.

Update #1: Stacktrace:

   at System.Text.Json.ThrowHelper.ThrowJsonReaderException(Utf8JsonReader& json, ExceptionResource resource, Byte nextByte, ReadOnlySpan`1 bytes)
   at System.Text.Json.Utf8JsonReader.ReadSingleSegment()
   at System.Text.Json.Utf8JsonReader.Read()
   at System.Text.Json.JsonDocument.Parse(ReadOnlySpan`1 utf8JsonSpan, Utf8JsonReader reader, MetadataDb& database, StackRowStack& stack)
   at System.Text.Json.JsonDocument.Parse(ReadOnlyMemory`1 utf8Json, JsonReaderOptions readerOptions, Byte[] extraRentedBytes)
   at System.Text.Json.JsonDocument.Parse(ReadOnlyMemory`1 json, JsonDocumentOptions options)
   at System.Text.Json.JsonDocument.Parse(String json, JsonDocumentOptions options)
   at Anonymized..ctor(String json) in Anonymized.cs:line 182
   at Anonymized.<>c__DisplayClass12_0.<TestCreateLogEntryFromJson_IllegalValues>b__0() in Anonymized.cs:line 206
   at NUnit.Framework.Assert.Throws(IResolveConstraint expression, TestDelegate code, String message, Object[] args)

Update #2: I went to see if there would be some nugets that I'm missing. I found System.Text.Json as a nuget (although it was already accessible, I used System.Text.JsonSerializer successfully within the test file). I added it and now I get the actual problem: It is inaccessible due to its protection level.

using System.Text.Json;

namespace System.Text.Json
{
    internal sealed class JsonReaderException : JsonException
    {
        public JsonReaderException(string message, long lineNumber, long bytePositionInLine);
    }
}

This doesn't however directly solve, how I can catch this within Assert.Throws<System.Text.Json.JsonReaderException>?


Solution

  • The fact that System.Text.Json.JsonReaderException is currently internal indicates that Microsoft may modify or remove this type at any time, and users of System.Text.Json should not depend on the continued existence of this class as a subclass of the public JsonException. Indeed, the documentation for Utf8JsonReader states only that

    When Utf8JsonReader encounters invalid JSON, it throws a JsonException with basic error information like line number and byte position on the line.

    And the code comments for JsonReaderException state:

    // This class exists because the serializer needs to catch reader-originated exceptions in order to throw JsonException which has Path information.
    

    Instead, assert that the exception thrown is a JsonException by using Is.InstanceOf<JsonException>()

    Assert.Throws(Is.InstanceOf<JsonException>(), () => JsonDocument.Parse(json).Dispose());
    

    If for some reason you must assert the specific exception type that was thrown, you could check the exception's full type name by taking advantage of the fact that Assert.Throws() returns the thrown exception:

    Assert.AreEqual("System.Text.Json.JsonReaderException",
                    Assert.Throws(Is.InstanceOf<JsonException>(), () => JsonDocument.Parse(json).Dispose()).GetType().FullName);
    

    Or you could use NUnit's custom constraint mechanism and introduce a FullTypeNameConstraint like the following:

    using NUnit.Framework;
    using NUnit.Framework.Constraints;
    
    public class FullTypeNameConstraint : Constraint
    {
        readonly string expectedFullTypeName;
    
        public FullTypeNameConstraint(string expectedFullTypeName) : base(expectedFullTypeName) => this.expectedFullTypeName = expectedFullTypeName;
    
        public override string DisplayName => "FullTypeNameOf";
    
        public override ConstraintResult ApplyTo<TActual>(TActual actual)
        {
            var actualTypeName = actual?.GetType().FullName;
            return new ConstraintResult(this, actualTypeName, actualTypeName == expectedFullTypeName);
        }
    }
    
    public class Is : NUnit.Framework.Is
    {
        public static FullTypeNameConstraint FullTypeNameOf(string expectedFullTypeName) => new FullTypeNameConstraint(expectedFullTypeName);
    }   
    
    public static class CustomConstraintExtensions
    {
        public static FullTypeNameConstraint FullTypeNameOf(this ConstraintExpression expression, string expectedFullTypeName)
        {
            var constraint = new FullTypeNameConstraint(expectedFullTypeName);
            expression.Append(constraint);
            return constraint;
        }
    }    
    

    And then you will be able to do:

    Assert.Throws(Is.FullTypeNameOf("System.Text.Json.JsonReaderException"), () => JsonDocument.Parse(json).Dispose());
    

    But honestly I wouldn't recommend it.

    As an aside, JsonDocument is disposable and in fact needs to be disposed to free up pooled memory for reuse.

    Demo fiddle here: https://dotnetfiddle.net/0dLxeO.