Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit e1fe9cc

Browse files
committed
Add support for deserializing arbitrary JSON into late-bound object
1 parent 8933521 commit e1fe9cc

File tree

6 files changed

+107
-41
lines changed

6 files changed

+107
-41
lines changed

src/ServiceStack.Text/Common/DeserializeType.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,14 @@ public static object ObjectStringToType(StringSegment strType)
7474
}
7575
}
7676

77-
return (JsConfig.TryToParsePrimitiveTypeValues
78-
? ParsePrimitive(strType.Value)
79-
: null) ?? Serializer.UnescapeString(strType).Value;
77+
var primitiveType = JsConfig.TryToParsePrimitiveTypeValues ? ParsePrimitive(strType.Value) : null;
78+
if (primitiveType != null)
79+
return primitiveType;
80+
81+
if (Serializer.ObjectDeserializer != null)
82+
return Serializer.ObjectDeserializer(strType);
83+
84+
return Serializer.UnescapeString(strType).Value;
8085
}
8186

8287
public static Type ExtractType(string strType) => ExtractType(new StringSegment(strType));

src/ServiceStack.Text/Common/ITypeSerializer.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ namespace ServiceStack.Text.Common
1212
{
1313
public interface ITypeSerializer
1414
{
15+
Func<StringSegment, object> ObjectDeserializer { get; set; }
16+
1517
bool IncludeNullValues { get; }
1618
bool IncludeNullValuesInDictionaries { get; }
1719
string TypeAttrInObject { get; }

src/ServiceStack.Text/Common/JsWriter.cs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -154,24 +154,24 @@ public static void WriteEnumFlags(TextWriter writer, object enumFlagValue)
154154
}
155155
}
156156

157-
public static void AssertAllowedRuntimeType(Type type)
157+
public static bool ShouldAllowRuntmieType(Type type)
158158
{
159159
if (!JsState.IsRuntimeType)
160-
return;
160+
return true;
161161

162162
if (JsConfig.AllowRuntimeType?.Invoke(type) == true)
163-
return;
163+
return true;
164164

165165
var allowAttributesNamed = JsConfig.AllowRuntimeTypeWithAttributesNamed;
166166
if (allowAttributesNamed?.Count > 0)
167167
{
168-
var OAttrs = type.AllAttributes();
169-
foreach (var oAttr in OAttrs)
168+
var oAttrs = type.AllAttributes();
169+
foreach (var oAttr in oAttrs)
170170
{
171171
var attr = oAttr as Attribute;
172172
if (attr == null) continue;
173173
if (allowAttributesNamed.Contains(attr.GetType().Name))
174-
return;
174+
return true;
175175
}
176176
}
177177

@@ -182,11 +182,17 @@ public static void AssertAllowedRuntimeType(Type type)
182182
foreach (var interfaceType in interfaces)
183183
{
184184
if (allowInterfacesNamed.Contains(interfaceType.Name))
185-
return;
185+
return true;
186186
}
187187
}
188188

189-
throw new NotSupportedException($"{type.Name} is not an allowed Runtime Type. Whitelist Type with [RuntimeSerializable] or IRuntimeSerializable.");
189+
return false;
190+
}
191+
192+
public static void AssertAllowedRuntimeType(Type type)
193+
{
194+
if (!ShouldAllowRuntmieType(type))
195+
throw new NotSupportedException($"{type.Name} is not an allowed Runtime Type. Whitelist Type with [RuntimeSerializable] or IRuntimeSerializable.");
190196
}
191197
}
192198

src/ServiceStack.Text/Json/JsonTypeSerializer.cs

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,40 +21,21 @@ public class JsonTypeSerializer
2121
{
2222
public static ITypeSerializer Instance = new JsonTypeSerializer();
2323

24-
public bool IncludeNullValues
25-
{
26-
get { return JsConfig.IncludeNullValues; }
27-
}
24+
public Func<StringSegment, object> ObjectDeserializer { get; set; }
2825

29-
public bool IncludeNullValuesInDictionaries
30-
{
31-
get { return JsConfig.IncludeNullValuesInDictionaries; }
32-
}
26+
public bool IncludeNullValues => JsConfig.IncludeNullValues;
3327

34-
public string TypeAttrInObject
35-
{
36-
get { return JsConfig.JsonTypeAttrInObject; }
37-
}
28+
public bool IncludeNullValuesInDictionaries => JsConfig.IncludeNullValuesInDictionaries;
3829

39-
internal static string GetTypeAttrInObject(string typeAttr)
40-
{
41-
return string.Format("{{\"{0}\":", typeAttr);
42-
}
30+
public string TypeAttrInObject => JsConfig.JsonTypeAttrInObject;
4331

44-
public WriteObjectDelegate GetWriteFn<T>()
45-
{
46-
return JsonWriter<T>.WriteFn();
47-
}
32+
internal static string GetTypeAttrInObject(string typeAttr) => $"{{\"{typeAttr}\":";
4833

49-
public WriteObjectDelegate GetWriteFn(Type type)
50-
{
51-
return JsonWriter.GetWriteFn(type);
52-
}
34+
public WriteObjectDelegate GetWriteFn<T>() => JsonWriter<T>.WriteFn();
5335

54-
public TypeInfo GetTypeInfo(Type type)
55-
{
56-
return JsonWriter.GetTypeInfo(type);
57-
}
36+
public WriteObjectDelegate GetWriteFn(Type type) => JsonWriter.GetWriteFn(type);
37+
38+
public TypeInfo GetTypeInfo(Type type) => JsonWriter.GetTypeInfo(type);
5839

5940
/// <summary>
6041
/// Shortcut escape when we're sure value doesn't contain any escaped chars
@@ -98,13 +79,13 @@ public void WriteBuiltIn(TextWriter writer, object value)
9879

9980
public void WriteObjectString(TextWriter writer, object value)
10081
{
101-
JsonUtils.WriteString(writer, value != null ? value.ToString() : null);
82+
JsonUtils.WriteString(writer, value?.ToString());
10283
}
10384

10485
public void WriteFormattableObjectString(TextWriter writer, object value)
10586
{
10687
var formattable = value as IFormattable;
107-
JsonUtils.WriteString(writer, formattable != null ? formattable.ToString(null, CultureInfo.InvariantCulture) : null);
88+
JsonUtils.WriteString(writer, formattable?.ToString(null, CultureInfo.InvariantCulture));
10889
}
10990

11091
public void WriteException(TextWriter writer, object value)

src/ServiceStack.Text/Jsv/JsvTypeSerializer.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public class JsvTypeSerializer
1818
{
1919
public static ITypeSerializer Instance = new JsvTypeSerializer();
2020

21+
public Func<StringSegment, object> ObjectDeserializer { get; set; }
22+
2123
public bool IncludeNullValues => false;
2224

2325
public bool IncludeNullValuesInDictionaries => false;

tests/ServiceStack.Text.Tests/RuntimeSerializtionTests.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
using NUnit.Framework;
66
using ServiceStack.Auth;
77
using ServiceStack.Messaging;
8+
using ServiceStack.Templates;
9+
using ServiceStack.Text.Json;
810

911
namespace ServiceStack.Text.Tests
1012
{
@@ -15,6 +17,16 @@ public class RuntimeObject
1517

1618
public class AType {}
1719

20+
public class JsonType
21+
{
22+
public int Int { get; set; }
23+
public string String { get; set; }
24+
public bool Bool { get; set; }
25+
public string Null { get; set; }
26+
public List<object> List { get; set; }
27+
public Dictionary<string, object> Dictionary { get; set; }
28+
}
29+
1830
[DataContract]
1931
public class DtoType { }
2032

@@ -215,5 +227,63 @@ public void Does_allow_Unknown_Type_in_RequestLogEntry()
215227
var fromJson = json.FromJson<RequestLogEntry>();
216228
Assert.That(fromJson.RequestDto.GetType(), Is.EqualTo(typeof(AType)));
217229
}
230+
231+
[Test]
232+
public void Can_deserialize_object_with_unknown_JSON_into_object_type()
233+
{
234+
using (JsConfig.With(excludeTypeInfo:true))
235+
{
236+
JsonTypeSerializer.Instance.ObjectDeserializer = segment =>
237+
{
238+
segment.ParseNextToken(out object value, out _);
239+
return value;
240+
};
241+
242+
var dto = new RuntimeObject
243+
{
244+
Object = new JsonType
245+
{
246+
Int = 1,
247+
String = "foo",
248+
Bool = true,
249+
List = new List<object> { new JsonType{Int = 1, String = "foo", Bool = true} },
250+
Dictionary = new Dictionary<string, object> {{ "key", new JsonType{Int = 1, String = "foo", Bool = true} }},
251+
}
252+
};
253+
254+
var json = dto.ToJson();
255+
json.Print();
256+
Assert.That(json, Is.EqualTo(@"{""Object"":{""Int"":1,""String"":""foo"",""Bool"":true,
257+
""List"":[{""Int"":1,""String"":""foo"",""Bool"":true}],
258+
""Dictionary"":{""key"":{""Int"":1,""String"":""foo"",""Bool"":true}}}}".Replace("\r","").Replace("\n", "").Replace(" ","")));
259+
260+
// into object
261+
var fromJson = json.FromJson<object>();
262+
var jsonObj = (Dictionary<string, object>)fromJson;
263+
var jsonType = (Dictionary<string, object>)jsonObj["Object"];
264+
Assert.That(jsonType["Int"], Is.EqualTo(1));
265+
Assert.That(jsonType["String"], Is.EqualTo("foo"));
266+
Assert.That(jsonType["Bool"], Is.EqualTo(true));
267+
var jsonList = (List<object>)jsonType["List"];
268+
Assert.That(((Dictionary<string, object>)jsonList[0])["Int"], Is.EqualTo(1));
269+
var jsonDict = (Dictionary<string, object>)jsonType["Dictionary"];
270+
Assert.That(((Dictionary<string, object>)jsonDict["key"])["Int"], Is.EqualTo(1));
271+
272+
// into DTO with Object property
273+
var dtoFromJson = json.FromJson<RuntimeObject>();
274+
jsonType = (Dictionary<string, object>) dtoFromJson.Object;
275+
Assert.That(jsonType["Int"], Is.EqualTo(1));
276+
Assert.That(jsonType["Int"], Is.EqualTo(1));
277+
Assert.That(jsonType["String"], Is.EqualTo("foo"));
278+
Assert.That(jsonType["Bool"], Is.EqualTo(true));
279+
jsonList = (List<object>)jsonType["List"];
280+
Assert.That(((Dictionary<string, object>)jsonList[0])["Int"], Is.EqualTo(1));
281+
jsonDict = (Dictionary<string, object>)jsonType["Dictionary"];
282+
Assert.That(((Dictionary<string, object>)jsonDict["key"])["Int"], Is.EqualTo(1));
283+
284+
JsonTypeSerializer.Instance.ObjectDeserializer = null;
285+
}
286+
}
287+
218288
}
219289
}

0 commit comments

Comments
 (0)