プロジェクトはAsp.Net Web API Webサービスです。
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
がBaseTypeMember
のインスタンスと等しいDerivedType
インスタンスを逆シリアル化すると、その型のインスタンスに逆シリアル化されます。
レコードの場合、これらのJSONオブジェクトには'$type'
フィールド(完全な.Netタイプ名ではない)の仮想タイプ名が含まれるため、シリアル化および非シリアル化できるタイプを正確に制御しながら、JSONでタイプを同時にサポートできます。
これは、リクエストから値をデシリアライズするのに非常によく機能します。シリアル化に問題があります。リンクされたSOを見ると、実際、トップの回答からリンクされているJson.Netのディスカッションを見ると、私が使用しているベースコードは完全に逆シリアル化を対象としていることがわかります。シリアライザの手動作成を示す使用例を示します。これによってJsonConverter
実装がテーブルにもたらされましたJsonCreationConverter<T>
は単にNotImplementedException
をスローします。
さて、Web APIがリクエストに単一のフォーマッタを使用する方法のため、WriteObject
メソッドに「標準」のシリアル化を実装する必要があります。
この時点で、プロジェクトのこの部分に着手する前に、everythingを適切にシリアル化していたことを強調する必要がありますエラーなし。
だから私はこれをしました:
public override void WriteJson(JsonWriter writer,
object value,
JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
しかし、私はJsonSerializationException
を取得します:Self referencing loop detected with type 'DerivedType'
、オブジェクトの1つがシリアル化されるとき。繰り返しますが、コンバーター属性を削除すると(カスタム作成が無効になります)、正常に機能します...
これは、私のシリアライゼーションコードが実際に同じオブジェクトでコンバーターを再度トリガーし、次にシリアライザを再び呼び出すことを意味していると感じています-吐き気です。 確認済み-私の回答を参照してください
では、どのコードがWriteObject
で記述すべきかは、機能するのと同じ「標準」シリアル化を行うでしょうか?
まあこれは楽しかった...
例外のスタックトレースをさらに詳しく見てみると、メソッドJsonSerializerInternalWriter.SerializeConvertable
が2回あることに気付きました。実際、スタックの一番上にあるメソッド1-JsonSerializerInternalWriter.CheckForCircularReference
の呼び出し-ターンは例外を投げていました。ただし、これは、自分のコンバーターのWrite
メソッドの呼び出し元でもありました。
だからシリアライザがやっているように見えるでしょう:
したがって、この場合、Json.Netは私のコンバーターを呼び出し、次にJson.Netシリアライザーを呼び出します。Json.Netシリアライザーは、渡されたオブジェクトを既にシリアル化していることがわかるため、爆破します。
DLLでILSpyを開き(はい、それがオープンソースであることはわかっていますが、「呼び出し元」機能が必要です!)、コールスタックをSerializeConvertable
からJsonSerializerInternalWriter.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シリアル化を行うつもりで、それをコンバーターand一部またはすべての状況で標準のシリアル化メカニズムにフォールバックする予定です。フレームワークをだまして、循環参照を格納しようとしていると考えてしまうので、それはできません。
私はReferenceLoopHandling
メンバーを操作しようとしましたが、Ignore
に指示した場合、何もシリアル化されず、保存するように指示した場合、当然のことながら、スタックオーバーフローが発生しました。
これはJson.Netのバグである可能性があります-申し訳ありませんが、これはEdgeケースの多くであり、宇宙のエッジから落ちる危険があります-しかし、この状況に陥ると、一種の行き詰まりになります!
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);
}
}
自分でこれに出会ったばかりで、欲求不満で髪を抜いていた!
この問題を解決するために、次の方法でうまくいきましたが、CanWrite
のソリューションを見逃したため、より複雑な回避策です。
JsonConverter
属性を削除します。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サポートにはFramework 4.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);
}
}
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
私は親/子コレクションで同じ問題を抱えていて、私の記事を解決した投稿を見つけました。親コレクションアイテムのリストを表示したいだけで、子データは必要なかったので、次を使用するとうまくいきました。
JsonConvert.SerializeObject(ResultGroups, Formatting.None,
new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
また、次のJson.NET codplexページも参照しています。
これは誰かを助けるかもしれませんが、私の場合、Equalsメソッドをオーバーライドして、オブジェクトを値型として扱うようにしました。私の調査で、JSON.NETは次のようになっていないことがわかりました。
鉱山は単純な間違いであり、このトピックの解決策とは何の関係もありませんでした。
このトピックはグーグルの最初のページだったので、他の人が私と同じ問題を抱えている場合に備えて、ここに投稿します。
dynamic table = new ExpandoObject();
..
..
table.rows = table; <<<<<<<< I assigned same dynamic object to itself.