43

このプロジェクトは、Asp.Net WebAPIWebサービスです。

Jsonとの間でシリアル化できるようにする必要がある型階層があるため、このSOからコードを取得しました:JSON.NETでカスタムJsonConverterを実装して、基本クラスオブジェクトのリストを逆シリアル化するにはどうすればよいですか?、そしてコンバーターを私の階層の基本クラスに適用しました。このようなもの(無関係を隠すためにここに擬似コードがあります):

[JsonConverter(typeof(TheConverter))]
public class BaseType
{
    // note the base of this type here is from the linked SO above
    private class TheConverter : JsonCreationConverter<BaseType>
    {
        protected override BaseType Create(Type objectType, JObject jObject)
        {
            Type actualType = GetTypeFromjObject(jObject); /*method elided*/
            return (BaseType)Activator.CreateInstance(actualType);
        }
    }
}

public class RootType
{
    public BaseType BaseTypeMember { get; set; }
}

public class DerivedType : BaseType
{

}

したがって、のインスタンスと等しいRootTypeインスタンスを逆シリアル化すると、そのタイプのインスタンスに逆シリアル化されます。BaseTypeMemberDerivedType

ちなみに、これらのJSONオブジェクトには、'$type'仮想型名(完全な.Net型名ではない)を含むフィールドが含まれているため、シリアル化および逆シリアル化できる型を正確に制御しながら、JSONで型を同時にサポートできます。

これで、リクエストから値を逆シリアル化するのに非常にうまく機能します。しかし、シリアル化に問題があります。リンクされたSO、そして実際にトップアンサーからリンクされているJson.Netの議論を見ると、私が使用しているベースコードは完全に逆シリアル化を対象としていることがわかります。シリアライザーの手動作成を示すその使用例を示します。これJsonConverterによってテーブルにもたらされた実装は、JsonCreationConverter<T>単に。をスローしNotImplementedExceptionます。

ここで、Web APIがリクエストに単一のフォーマッターを使用する方法のため、WriteObjectメソッドに「標準」のシリアル化を実装する必要があります。

この時点で、プロジェクトのこの部分に着手する前に、すべてがエラーなしで適切にシリアル化されたことを強調する必要があります。

だから私はこれをしました:

public override void WriteJson(JsonWriter writer, 
  object value, 
  JsonSerializer serializer)
{
    serializer.Serialize(writer, value);
}

しかし、オブジェクトの1つがシリアル化されると、 JsonSerializationException:が表示されます。Self referencing loop detected with type 'DerivedType'繰り返しますが、コンバーター属性を削除すると(カスタム作成を無効にすると)、正常に機能します...

これは、私のシリアル化コードが実際に同じオブジェクトでコンバーターを再度トリガーしていることを意味していると感じています。これにより、シリアライザーが再び呼び出されます。確認済み-私の答えを参照してください

では、機能するのと同じ「標準」シリアル化を実行するために、どのコードを記述する必要がありますか?WriteObject

4

7 に答える 7

56

さて、これは楽しかったです...

例外のスタックトレースを詳しく見ると、メソッドJsonSerializerInternalWriter.SerializeConvertableが2回存在していることに気付きました。実際、スタックの最上位から1つ離れたメソッド(呼び出しJsonSerializerInternalWriter.CheckForCircularReference)が例外をスローしていました。Writeしかし、それは私自身のコンバーターのメソッドへの呼び出しのソースでもありました。

したがって、シリアライザーは次のことを行っているように見えます。

  • 1)オブジェクトにコンバーターがある場合
    • 1a)循環参照の場合はスロー
    • 1b)コンバータのWriteメソッドを呼び出す
  • 2)その他
    • 2a)内部シリアライザーを使用する

したがって、この場合、Json.Netはコンバーターを呼び出しており、コンバーターはJson.Netシリアライザーを呼び出しています。コンバーターは、渡されたオブジェクトが既にシリアル化されていることを確認したため、爆発します。

DLLでILSpyを開き(はい、オープンソースであることはわかっていますが、「呼び出し元」機能が必要です!)、呼び出しスタックをからに移動するとSerializeConvertableJsonSerializerInternalWriter.SerializeValueコンバーターを使用する必要があるかどうかを検出するコードが開始点のすぐ近くにあります。

if (((jsonConverter = ((member != null) ? member.Converter : null)) != null 
   || (jsonConverter = ((containerProperty != null) ? containerProperty.ItemConverter 
                                                    : null)) != null 
   || (jsonConverter = ((containerContract != null) ? containerContract.ItemConverter 
                                                    : null)) != null 
   || (jsonConverter = valueContract.Converter) != null 
   || (jsonConverter = 
       this.Serializer.GetMatchingConverter(valueContract.UnderlyingType)) != null 
   || (jsonConverter = valueContract.InternalConverter) != null) 
   && jsonConverter.CanWrite)
{
    this.SerializeConvertable(writer, jsonConverter, value, valueContract, 
                              containerContract, containerProperty);
    return;
}

ありがたいことに、ifステートメントの最後の条件が私の問題の解決策を提供します。私がしなければならなかったのは、質問のリンクされたSOのコードからコピーされたベースコンバーターまたは派生したもののいずれかに以下を追加することでした。

public override bool CanWrite
{
    get
    {
        return false;
    }
}

そして今、それはすべてうまく機能します。

ただし、これの結果は、オブジェクトにカスタムJSONシリアル化を設定し、それをコンバーターで注入し、一部またはすべての状況で標準のシリアル化メカニズムにフォールバックする場合です。フレームワークをだまして循環参照を格納しようとしていると思わせるため、できません。

メンバーを操作しようとしましたReferenceLoopHandlingが、メンバーに伝えてIgnoreも何もシリアル化されず、メンバーを保存するように伝えた場合、当然のことながら、スタックオーバーフローが発生しました。

これはJson.Netのバグである可能性があります-申し分なく、それは宇宙の端から落ちる危険があるほどのエッジケースです-しかし、あなたがこの状況に陥った場合、あなたは一種の行き詰まりです!!

于 2012-09-07T09:31:51.933 に答える
8

Newtonsoft.Jsonのバージョン4.5.7.15008を使用してこの問題が発生しました。ここで提供されているすべてのソリューションを他のソリューションと一緒に試しました。以下のコードを使用して問題を解決しました。基本的には、別のJsonSerializerを使用してシリアル化を実行できます。作成されたJsonSerializerにはコンバーターが登録されていないため、再入場/例外は回避されます。他の設定またはContractResolverを使用する場合は、作成されたシリアル化に手動で設定する必要があります。これに対応するために、いくつかのコンストラクター引数をCustomConverterクラスに追加できます。

    public class CustomConverter : JsonConverter
    {
        /// <summary>
        /// Use a privately create serializer so we don't re-enter into CanConvert and cause a Newtonsoft exception
        /// </summary>
        private readonly JsonSerializer noRegisteredConvertersSerializer = new JsonSerializer();

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            bool meetsCondition = false; /* add condition here */
            if (!meetsCondition)
                writer.WriteNull();
            else
                noRegisteredConvertersSerializer.Serialize(writer, value);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override bool CanConvert(Type objectType)
        {
            // example: register accepted conversion types here
            return typeof(IDictionary<string, object>).IsAssignableFrom(objectType);
        }
    }
于 2014-09-16T11:46:03.010 に答える
2

自分でこれに出くわし、欲求不満で髪を抜いていた!

この問題を解決するために、次のことがうまくいきましたが、解決策を逃したためCanWrite、より複雑な回避策になります。

  • Converterを使用している既存のクラスのコピーを作成し、それを別の名前で呼び出します。
  • JsonConverterコピーの属性を削除します。
  • 元のクラスと同じタイプのパラメーターを受け取る新しいクラスにコンストラクターを作成します。コンストラクターを使用して、後のシリアル化に必要な値をコピーします。
  • ConverterのWriteJsonメソッドで、値をダミータイプに変換し、代わりにそのタイプをシリアル化します。

たとえば、これは私の元のクラスに似ています。

[JsonConverter(typeof(MyResponseConverter))]
public class MyResponse
{
    public ResponseBlog blog { get; set; }
    public Post[] posts { get; set; }
}

コピーは次のようになります。

public class FakeMyResponse
{
    public ResponseBlog blog { get; set; }
    public Post[] posts { get; set; }

    public FakeMyResponse(MyResponse response)
    {
        blog = response.blog;
        posts = response.posts;
    }
}

WriteJsonは次のとおりです。

public override void WriteJson(JsonWriter writer, object value,
    JsonSerializer serializer)
{
    if (CanConvert(value.GetType()))
    {
        FakeMyResponse response = new FakeMyResponse((MyResponse)value);
        serializer.Serialize(writer, response);
    }
}

編集:

OPは、 Expandoを使用することが別の可能な解決策になる可能性があることを指摘しました。これはうまく機能し、新しいクラスを作成する手間を省きますが、DLRサポートにはFramework4.0以降が必要です。アプローチは、新しいものを作成してからdynamic ExpandoObject、メソッドでそのプロパティをWriteJson直接初期化して、コピーを作成することです。例:

public override void WriteJson(JsonWriter writer, object value,
    JsonSerializer serializer)
{
    if (CanConvert(value.GetType()))
    {
        var response = (MyResponse)value;
        dynamic fake = new System.Dynamic.ExpandoObject();
        fake.blog = response.blog;
        fake.posts = response.posts;
        serializer.Serialize(writer, fake);
    }
}
于 2012-09-09T02:35:02.293 に答える
1

親/子コレクションで同じ問題が発生し、その投稿が私のケースを解決していることがわかりました。親コレクションアイテムのリストのみを表示したかったので、子データは必要ありませんでした。したがって、以下を使用すると、正常に機能しました。

JsonConvert.SerializeObject(ResultGroups, Formatting.None,
                        new JsonSerializerSettings()
                        { 
                            ReferenceLoopHandling = ReferenceLoopHandling.Ignore
                        });

また、次のJson.NETcodplexページも参照しています。

http://json.codeplex.com/discussions/272371

于 2012-11-25T10:04:34.723 に答える
1

IMO、これはライブラリの重大な制限です。解決策は非常に簡単ですが、すぐには思いつかなかったことは認めます。解決策は次のように設定することです。

.ReferenceLoopHandling = ReferenceLoopHandling.Serialize

これは、至る所で文書化されているように、自己参照エラーを排除し、スタックオーバーフローに置き換えます。私の場合、書き込み機能が必要だったので、CanWriteをfalseに設定することはできませんでした。最後に、シリアライザーへの呼び出しが(無限の)再帰を引き起こしていることがわかっているときに、CanConvert呼び出しを保護するためのフラグを設定しました。

    Public Class ReferencingObjectConverter : Inherits JsonConverter

        Private _objects As New HashSet(Of String)
        Private _ignoreNext As Boolean = False

        Public Overrides Function CanConvert(objectType As Type) As Boolean
            If Not _ignoreNext Then
                Return GetType(IElement).IsAssignableFrom(objectType) AndAlso Not GetType(IdProperty).IsAssignableFrom(objectType)
            Else
                _ignoreNext = False
                Return False
            End If
        End Function

        Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)

            Try
                If _objects.Contains(CType(value, IElement).Id.Value) Then 'insert a reference to existing serialized object
                    serializer.Serialize(writer, New Reference With {.Reference = CType(value, IElement).Id.Value})
                Else 'add to my list of processed objects
                    _objects.Add(CType(value, IElement).Id.Value)
                    'the serialize will trigger a call to CanConvert (which is how we got here it the first place)
                    'and will bring us right back here with the same 'value' parameter (and SO eventually), so flag
                    'the CanConvert function to skip the next call.
                    _ignoreNext = True
                    serializer.Serialize(writer, value)
                End If
            Catch ex As Exception
                Trace.WriteLine(ex.ToString)
            End Try

        End Sub

        Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
            Throw New NotImplementedException()
        End Function

        Private Class Reference
            Public Property Reference As String
        End Class

    End Class
于 2017-10-26T07:24:22.023 に答える
0

これは誰かを助けるかもしれませんが、私の場合、オブジェクトを値型として扱うためにEqualsメソッドをオーバーライドしようとしていました。私の調査では、JSON.NETはこれを好まないことがわかりました。

JSON.NET自己参照エラー

于 2014-02-03T06:19:04.567 に答える
0

私のは単純な間違いであり、このトピックの解決策とは何の関係もありませんでした。

このトピックはグーグルの最初のページだったので、他の人が私と同じ問題を抱えている場合に備えて、ここに投稿しています。

dynamic table = new ExpandoObject();
..
..
table.rows = table; <<<<<<<< I assigned same dynamic object to itself. 
于 2015-10-29T16:08:17.860 に答える