Parcourir la source

Readme + misc fixes

Brad Robinson il y a 11 ans
Parent
commit
0b8ab862fc
5 fichiers modifiés avec 543 ajouts et 35 suppressions
  1. 113 35
      PetaJson.cs
  2. 2 0
      TestCases/TestAbstractTypes.cs
  3. 1 0
      TestCases/TestCases.csproj
  4. 76 0
      TestCases/TestOptions.cs
  5. 351 0
      readme.md

+ 113 - 35
PetaJson.cs

@@ -12,40 +12,85 @@ using System.Dynamic;
 
 namespace PetaJson
 {
+    [Flags]
+    public enum JsonOptions
+    {
+        None = 0,
+        WriteWhitespace  = 0x00000001,
+        DontWriteWhitespace = 0x00000002,
+        StrictParser = 0x00000004,
+        NonStrictParser = 0x00000008,
+    }
+
     public static class Json
     {
+        static Json()
+        {
+            WriteWhitespaceDefault = true;
+            StrictParserDefault = false;
+        }
+
+        public static bool WriteWhitespaceDefault
+        {
+            get;
+            set;
+        }
+
+        public static bool StrictParserDefault
+        {
+            get;
+            set;
+        }
+
+        static JsonOptions ResolveOptions(JsonOptions options)
+        {
+            JsonOptions resolved = JsonOptions.None;
+
+            if ((options & (JsonOptions.WriteWhitespace|JsonOptions.DontWriteWhitespace))!=0)
+                resolved |= options & (JsonOptions.WriteWhitespace | JsonOptions.DontWriteWhitespace);
+            else
+                resolved |= WriteWhitespaceDefault ? JsonOptions.WriteWhitespace : JsonOptions.DontWriteWhitespace;
+
+            if ((options & (JsonOptions.StrictParser | JsonOptions.NonStrictParser)) != 0)
+                resolved |= options & (JsonOptions.StrictParser | JsonOptions.NonStrictParser);
+            else
+                resolved |= StrictParserDefault ? JsonOptions.StrictParser : JsonOptions.NonStrictParser;
+
+            return resolved;
+        }
+
         // Write an object to a text writer
-        public static void Write(TextWriter w, object o)
+        public static void Write(TextWriter w, object o, JsonOptions options = JsonOptions.None)
         {
-            var writer = new Internal.Writer(w);
+            var writer = new Internal.Writer(w, ResolveOptions(options));
             writer.WriteValue(o);
         }
 
         // Write an object to a file
-        public static void WriteFile(string filename, object o)
+        public static void WriteFile(string filename, object o, JsonOptions options = JsonOptions.None)
         {
             using (var w = new StreamWriter(filename))
             {
-                Write(w, o);
+                Write(w, o, options);
             }
         }
 
         // Format an object as a json string
-        public static string Format(object o)
+        public static string Format(object o, JsonOptions options = JsonOptions.None)
         {
             var sw = new StringWriter();
-            var writer = new Internal.Writer(sw);
+            var writer = new Internal.Writer(sw, ResolveOptions(options));
             writer.WriteValue(o);
             return sw.ToString();
         }
 
         // Parse an object of specified type from a text reader
-        public static object Parse(TextReader r, Type type)
+        public static object Parse(TextReader r, Type type, JsonOptions options = JsonOptions.None)
         {
             Internal.Reader reader = null;
             try
             {
-                reader = new Internal.Reader(r);
+                reader = new Internal.Reader(r, ResolveOptions(options));
                 var retv = reader.Parse(type);
                 reader.CheckEOF();
                 return retv;
@@ -57,18 +102,18 @@ namespace PetaJson
         }
 
         // Parse an object of specified type from a text reader
-        public static T Parse<T>(TextReader r)
+        public static T Parse<T>(TextReader r, JsonOptions options = JsonOptions.None)
         {
-            return (T)Parse(r, typeof(T));
+            return (T)Parse(r, typeof(T), options);
         }
 
         // Parse from text reader into an already instantied object
-        public static void ParseInto(TextReader r, Object into)
+        public static void ParseInto(TextReader r, Object into, JsonOptions options = JsonOptions.None)
         {
             Internal.Reader reader = null;
             try
             {
-                reader = new Internal.Reader(r);
+                reader = new Internal.Reader(r, ResolveOptions(options));
                 reader.ParseInto(into);
                 reader.CheckEOF();
             }
@@ -79,48 +124,48 @@ namespace PetaJson
         }
 
         // Parse an object of specified type from a text reader
-        public static object ParseFile(string filename, Type type)
+        public static object ParseFile(string filename, Type type, JsonOptions options = JsonOptions.None)
         {
             using (var r = new StreamReader(filename))
             {
-                return Parse(r, type);
+                return Parse(r, type, options);
             }
         }
 
         // Parse an object of specified type from a text reader
-        public static T ParseFile<T>(string filename)
+        public static T ParseFile<T>(string filename, JsonOptions options = JsonOptions.None)
         {
             using (var r = new StreamReader(filename))
             {
-                return Parse<T>(r);
+                return Parse<T>(r, options);
             }
         }
 
         // Parse from text reader into an already instantied object
-        public static void ParseFileInto(string filename, Object into)
+        public static void ParseFileInto(string filename, Object into, JsonOptions options = JsonOptions.None)
         {
             using (var r = new StreamReader(filename))
             {
-                ParseInto(r, into);
+                ParseInto(r, into, options);
             }
         }
 
         // Parse an object from a string
-        public static object Parse(string data, Type type)
+        public static object Parse(string data, Type type, JsonOptions options = JsonOptions.None)
         {
-            return Parse(new StringReader(data), type);
+            return Parse(new StringReader(data), type, options);
         }
 
         // Parse an object from a string
-        public static T Parse<T>(string data)
+        public static T Parse<T>(string data, JsonOptions options = JsonOptions.None)
         {
-            return (T)Parse<T>(new StringReader(data));
+            return (T)Parse<T>(new StringReader(data), options);
         }
 
         // Parse from string into an already instantiated object
-        public static void ParseInto(string data, Object into)
+        public static void ParseInto(string data, Object into, JsonOptions options = JsonOptions.None)
         {
-            ParseInto(new StringReader(data), into);
+            ParseInto(new StringReader(data), into, options);
         }
 
         public static T Clone<T>(T source)
@@ -357,12 +402,14 @@ namespace PetaJson
                 });
             }
 
-            public Reader(TextReader r)
+            public Reader(TextReader r, JsonOptions options)
             {
-                _tokenizer = new Tokenizer(r);
+                _tokenizer = new Tokenizer(r, options);
+                _options = options;
             }
 
             Tokenizer _tokenizer;
+            JsonOptions _options;
 
             public JsonLineOffset CurrentTokenPosition
             {
@@ -658,7 +705,7 @@ namespace PetaJson
                 {
                     // Parse the key
                     string key = null;
-                    if (_tokenizer.CurrentToken == Token.Identifier)
+                    if (_tokenizer.CurrentToken == Token.Identifier && (_options & JsonOptions.StrictParser)==0)
                     {
                         key = _tokenizer.String;
                     }
@@ -686,7 +733,13 @@ namespace PetaJson
                     }
 
                     if (_tokenizer.SkipIf(Token.Comma))
+                    {
+                        if ((_options & JsonOptions.StrictParser) != 0 && _tokenizer.CurrentToken == Token.CloseBrace)
+                        {
+                            throw new InvalidDataException("Trailing commas not allowed in strict mode");
+                        }
                         continue;
+                    }
                     break;
                 }
             }
@@ -700,7 +753,13 @@ namespace PetaJson
                     callback();
 
                     if (_tokenizer.SkipIf(Token.Comma))
+                    {
+                        if ((_options & JsonOptions.StrictParser)!=0 && _tokenizer.CurrentToken==Token.CloseSquare)
+                        {
+                            throw new InvalidDataException("Trailing commas not allowed in strict mode");
+                        }
                         continue;
+                    }
                     break;
                 }
 
@@ -752,11 +811,12 @@ namespace PetaJson
 
             public static Dictionary<Type, Action<IJsonWriter, object>> _typeWriters = new Dictionary<Type, Action<IJsonWriter, object>>();
 
-            public Writer(TextWriter w)
+            public Writer(TextWriter w, JsonOptions options)
             {
                 _writer = w;
                 _atStartOfLine = true;
                 _needElementSeparator = false;
+                _options = options;
             }
 
             TextWriter _writer;
@@ -764,14 +824,19 @@ namespace PetaJson
             public int IndentLevel;
             bool _atStartOfLine;
             bool _needElementSeparator = false;
+            JsonOptions _options;
             char _currentBlockKind = '\0';
 
             public void NextLine()
             {
                 if (_atStartOfLine)
                     return;
-                WriteRaw("\n");
-                WriteRaw(new string('\t', IndentLevel));
+
+                if ((_options & JsonOptions.WriteWhitespace)!=0)
+                {
+                    WriteRaw("\n");
+                    WriteRaw(new string('\t', IndentLevel));
+                }
                 _atStartOfLine = true;
             }
 
@@ -806,7 +871,7 @@ namespace PetaJson
                     throw new InvalidOperationException("Attempt to write dictionary element when not in dictionary block");
                 NextElement();
                 WriteStringLiteral(key);
-                WriteRaw(": ");
+                WriteRaw(((_options & JsonOptions.WriteWhitespace)!=0) ? ": " : ":");
             }
 
             public void WriteRaw(string str)
@@ -1301,9 +1366,10 @@ namespace PetaJson
 
         public class Tokenizer
         {
-            public Tokenizer(TextReader r)
+            public Tokenizer(TextReader r, JsonOptions options)
             {
                 _reader = new RewindableTextReader(r);
+                _options = options;
 
                 _state._nextCharPos.Line = 0;
                 _state._nextCharPos.Offset = 0;
@@ -1315,6 +1381,7 @@ namespace PetaJson
             }
 
             RewindableTextReader _reader;
+            JsonOptions _options;
             StringBuilder _sb = new StringBuilder();
             ReaderState _state;
 
@@ -1432,8 +1499,16 @@ namespace PetaJson
                             switch (_state._currentChar)
                             {
                                 case '/':
-                                    NextChar();
-                                    state = State.Slash;
+                                    if ((_options & JsonOptions.StrictParser)!=0)
+                                    {
+                                        // Comments not support in strict mode
+                                        throw new InvalidDataException(string.Format("syntax error - unexpected character '{0}'", _state._currentChar));
+                                    }
+                                    else
+                                    {
+                                        NextChar();
+                                        state = State.Slash;
+                                    }
                                     break;
 
                                 case '\"':
@@ -1478,9 +1553,12 @@ namespace PetaJson
                                     NextChar();
                                     state = State.BlockComment;
                                     break;
+
+                                default:
+                                    throw new InvalidDataException("syntax error - unexpected character after slash");
                             }
+                            break;
 
-                            throw new InvalidDataException("syntax error - unexpected character after slash");
 
                         case State.SingleLineComment:
                             if (_state._currentChar == '\r' || _state._currentChar == '\n')

+ 2 - 0
TestCases/TestAbstractTypes.cs

@@ -72,6 +72,8 @@ namespace TestCases
             // Save it
             var json = Json.Format(shapes);
 
+            Console.WriteLine(json);
+
             // Check the object kinds were written out
             Assert.Contains(json, "\"kind\": \"Rectangle\"");
             Assert.Contains(json, "\"kind\": \"Ellipse\"");

+ 1 - 0
TestCases/TestCases.csproj

@@ -51,6 +51,7 @@
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="TestAbstractTypes.cs" />
     <Compile Include="TestCustomFormat.cs" />
+    <Compile Include="TestOptions.cs" />
     <Compile Include="TestsGeneral.cs" />
     <Compile Include="TestsReflection.cs" />
   </ItemGroup>

+ 76 - 0
TestCases/TestOptions.cs

@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using PetaTest;
+using PetaJson;
+
+namespace TestCases
+{
+    [TestFixture]
+    public class TestOptions
+    {
+        [Test]
+        public void TestWhitespace()
+        {
+            var o = new { x = 10, y = 20 };
+
+            var json = Json.Format(o, JsonOptions.WriteWhitespace);
+            Assert.Contains(json, "\n");
+            Assert.Contains(json, "\t");
+            Assert.Contains(json, ": ");
+
+            json = Json.Format(o, JsonOptions.DontWriteWhitespace);
+            Assert.DoesNotContain(json, "\n");
+            Assert.DoesNotContain(json, "\t");
+            Assert.DoesNotContain(json, ": ");
+        }
+        
+        [Test]
+        public void TestStrictComments()
+        {
+            var jsonWithCComment = "/* This is a comment*/ 23";
+            var jsonWithCppComment = "// This is a comment\n 23";
+
+            // Nonstrict parser allows it
+            var val = Json.Parse<int>(jsonWithCComment, JsonOptions.NonStrictParser);
+            Assert.AreEqual(val, 23);
+            val = Json.Parse<int>(jsonWithCppComment, JsonOptions.NonStrictParser);
+            Assert.AreEqual(val, 23);
+
+            // Strict parser
+            Assert.Throws<JsonParseException>(() => Json.Parse<int>(jsonWithCComment, JsonOptions.StrictParser));
+            Assert.Throws<JsonParseException>(() => Json.Parse<int>(jsonWithCppComment, JsonOptions.StrictParser));
+        }
+
+        [Test]
+        public void TestStrictTrailingCommas()
+        {
+            var arrayWithTrailingComma = "[1,2,]";
+            var dictWithTrailingComma = "{\"a\":1,\"b\":2,}";
+
+            // Nonstrict parser allows it
+            var array = Json.Parse<int[]>(arrayWithTrailingComma, JsonOptions.NonStrictParser);
+            Assert.AreEqual(array.Length, 2);
+            var dict = Json.Parse<Dictionary<string, object>>(dictWithTrailingComma, JsonOptions.NonStrictParser);
+            Assert.AreEqual(dict.Count, 2);
+
+            // Strict parser
+            Assert.Throws<JsonParseException>(() => Json.Parse<int>(arrayWithTrailingComma, JsonOptions.StrictParser));
+            Assert.Throws<JsonParseException>(() => Json.Parse<int>(dictWithTrailingComma, JsonOptions.StrictParser));
+        }
+
+        [Test]
+        public void TestStrictIdentifierKeys()
+        {
+            var data = "{a:1,b:2}";
+
+            var dict = Json.Parse<Dictionary<string, object>>(data, JsonOptions.NonStrictParser);
+            Assert.AreEqual(dict.Count, 2);
+            Assert.Contains(dict.Keys, "a");
+            Assert.Contains(dict.Keys, "b");
+
+            Assert.Throws<JsonParseException>(() => Json.Parse<Dictionary<string, object>>(data, JsonOptions.StrictParser));
+        }
+    }
+}

+ 351 - 0
readme.md

@@ -0,0 +1,351 @@
+# PetaJson
+
+PetaJson is a simple but flexible JSON library implemented in a single C# file.  Features include:
+
+* Standard JSON format parsing and generation
+* Supports strongly typed serialization through reflection, or custom code
+* Supports weakly typed serialization
+* Supports standard C# collection classes - no JSON specific classes (ie: no "JArray", "JObject" etc...)
+* Support for dynamic Expando (read) and anonymous types (write)
+* Custom formatting and parsing of any type
+* Support for serialization of abstract/virtual types
+* Directly reads from TextReader and writes to TextWriter and any underlying stream
+* Simple set of custom attributes to control serialization of types
+* Optional non-strict parsing allows comments, non-quoted dictionary keys and trailing commas (great for config files)
+* Optional pretty formatting
+* No dependencies, one file - PetaJson.cs
+* Works on .NET, Mono, Xamarin.Android, Xamarin.iOS.
+
+# Usage
+
+## Setup
+
+1. Add PetaJson.cs to your project
+2. That's it
+
+## Generating JSON
+
+To a string:
+
+	var o = new [] { 1, 2, 3 };
+	var json = Json.Format(o);
+
+or, write to a file
+
+	Json.WriteFile("MyData.json", o);
+
+using objects
+
+	class Person
+	{
+		string Name;
+		string Address;
+	};
+
+	var p = new Person() { Name = "Joe Sixpack", Address = "Home" };
+	var json = Json.Format(p);
+
+would yield:
+
+	{
+		"name": "Joe Sixpack",
+		"address": "Home"
+	}
+
+
+## Parsing JSON
+
+From a string:
+
+	int o = Json.Parse<int>("23");
+
+From string to a dynamic:
+
+	dynamic o = Json.Parse<object>("{\"apples\":\"red\", \"bananas\":\"yellow\" }");
+	string appleColor = o.apples;
+	string bananaColor = o.bananas;
+
+Weakly typed dictionary:
+
+	var dict = Json.Parse<Dictionary<string, object>>("{\"apples\":\"red\", \"bananas\":\"yellow\" }");
+
+Or an array:
+
+	int[] array = Json.Parse<int[]>("[1,2,3]");
+
+Strongly typed object:
+
+	Person person = Json.Parse<Person>(jsonFromPersonExampleAboveExample);
+	Console.WriteLine(person.Name);
+	Console.WriteLine(person.Address);
+
+From a file:
+
+	var person = Json.ParseFile<Person>("aboutme.json");
+
+Into an existing instance:
+
+	var person = new Person();
+	Json.ParseFileInto<Person>("aboutme.json", person);
+
+String into existing instance:
+
+	Json.ParseInto<Person>(jsonFromPersonExampleAboveExample, person);
+
+
+## Attributes
+
+PetaJson provides two attributes for decorating objects for serialization - [Json] and [JsonExclude].
+
+The Json attribute when applied to a class or struct marks all public properties and fields for serialization:
+
+	[Json]
+	class Person
+	{
+		public string Name;				// Serialized as "name"
+		public string Address;			// Serialized as "address"
+		public string alsoSerialized;	// Serialized as "alsoSerialized"
+		private string NotSerialized;
+	}
+
+When applied to one or more field/properties but not applied to the class itself, only the decorated members
+will be serialized:
+
+	class Person
+	{
+		[Json] public string Name;	// Serialized as "name":
+		public string Address;		// Not serialized
+	}
+
+By default, members are serialized using the same name as the field or properties, but with the first letter
+lowercased.  To override the serialized name, include the name as a parameter to the Json attribute:
+
+	class Person
+	{
+		[Json("PersonName")] public string Name; 	// Serialized as "PersonName"
+	}
+
+Use the JsonExclude attribute to exclude public fields/properties from serialization
+
+	[Json]
+	class Person
+	{
+		public string Name;		// Serialized as "name"
+		public string Address;	// Serialized as "Address"
+
+		[JsonExclude]			// Not serialized
+		public int Age
+		{
+			get { return calculateAge(); }
+		}
+	}
+
+
+## Custom Formatting
+
+Custom formatting can be used for any type.  Say we have the following type:
+
+    struct Point
+    {
+        public int X;
+        public int Y;
+    }
+
+We can serialize these as a string in the format "x,y" by registering a formatter
+
+    // Register custom formatter
+    Json.RegisterFormatter<Point>( (writer,point) => 
+    {
+        writer.WriteStringLiteral(string.Format("{0},{1}", point.X, point.Y));
+    });
+
+We also need a custom parser:
+
+    Json.RegisterParser<Point>( literal => {
+
+        var parts = ((string)literal).Split(',');
+        if (parts.Length!=2)
+            throw new InvalidDataException("Badly formatted point");
+
+        return new Point()
+        {
+            X = int.Parse(parts[0], CultureInfo.InvariantCulture),
+            Y = int.Parse(parts[0], CultureInfo.InvariantCulture),
+        };
+
+    });
+
+We can now format and parse Point structs:
+
+	// Format a Point
+	var json = Json.Format(new Point() { X= 10, Y=20 });		// "10,20"
+
+	// Parse a Point
+	var point = Json.Parse<Point>("\"10,20\"");
+
+Note that in this example we're formatting the point to a string literal containing both
+the X and Y components of the Point.  The reader and writer objects passed to the callbacks
+however have methods for reading and writing any arbitrary json format - I just happened to
+use a single string literal for this example.
+
+## Custom factories
+
+Suppose we have a class heirarchy something like this:
+
+    abstract class Shape
+    {
+    	// Omitted
+    }
+
+    class Rectangle : Shape
+    {
+    	// Omitted
+    }
+
+    class Ellipse : Shape
+    {
+    	// Omitted
+    }
+
+and we'd like to serialize a list of Shapes to Json like this:
+
+	[
+		{ "kind": "Rectangle", /* omitted */ },
+		{ "kind": "Shape", /* omitted */ },
+		// etc...
+	]
+
+In otherwords a key value in the dictionary for each object determines the type of object that 
+needs to be instantiated for each element.
+
+We can do this by firstly writing the element kind when saving using the IJsonWriting interface
+
+    abstract class Shape : IJsonWriting
+    {
+        // Override OnJsonWriting to write out the derived class type
+        void IJsonWriting.OnJsonWriting(IJsonWriter w)
+        {
+            w.WriteKey("kind");
+            w.WriteStringLiteral(GetType().Name);
+        }
+    }
+
+For parsing, we need to register a callback function that creates the correct instances of Shape:
+
+    // Register a type factory that can instantiate Shape objects
+    Json.RegisterTypeFactory(typeof(Shape), (reader, key) =>
+    {
+        // This method will be called back for each key in the json dictionary
+        // until an object instance is returned
+
+        // We saved the object type in a key called "kind", look for it
+        if (key != "kind")
+            return null;
+
+        // Read the next literal (which better be a string) and instantiate the object
+        return reader.ReadLiteral(literal =>
+        {
+            switch ((string)literal)
+            {
+                case "Rectangle": return new Rectangle();
+                case "Ellipse": return new Ellipse();
+                default:
+                    throw new InvalidDataException(string.Format("Unknown shape kind: '{0}'", literal));
+            }
+        });
+    });
+
+When attempting to deserialize Shape objects, the registered callback will be called with each 
+key in the dictionary until it returns an object instance.  In this case we're looking for a key
+named "kind" and we use it's value to create a new Rectangle or Ellipse instance.
+
+Note that the field used to hold the type (aka "kind") does not need to be the first field in the
+ in the dictionary being parsed. After instantiating the object, the input stream is rewound to the
+ start of the dictionary and then re-parsed directly into the instantiated object.  Note too that
+ the underlying stream doesn't need to support seeking - the rewind mechanism is implemented in 
+ PetaJson.
+
+## Serialization Events
+
+An object can get notifications of various events during the serialization/deserialization process
+by implementing one or more of the following interfaces:
+
+    // Called before loading via reflection
+    public interface IJsonLoading
+    {
+        void OnJsonLoading(IJsonReader r);
+    }
+
+    // Called after loading via reflection
+    public interface IJsonLoaded
+    {
+        void OnJsonLoaded(IJsonReader r);
+    }
+
+    // Called for each field while loading from reflection
+    // Return true if handled
+    public interface IJsonLoadField
+    {
+        bool OnJsonField(IJsonReader r, string key);
+    }
+
+    // Called when about to write using reflection
+    public interface IJsonWriting
+    {
+        void OnJsonWriting(IJsonWriter w);
+    }
+
+    // Called after written using reflection
+    public interface IJsonWritten
+    {
+        void OnJsonWritten(IJsonWriter w);
+    }
+
+
+For example, it's often necessary to wire up ownership chains on loaded subobjects:
+
+	class Drawing : IJsonLoaded
+	{
+		[Json]
+		public List<Shape> Shapes;
+
+		void IJsonLoaded.OnJsonLoaded()
+		{
+			// Shapes have been loaded, set back reference to self
+			foreach (var s in Shapes)
+			{
+				s.Owner = this;
+			}
+		}
+	}
+
+## Options
+
+PetaJson has a couple of options that can be set as global defaults:
+
+	Json.WriteWhitespaceDefault = true;		// Pretty formatting
+	Json.StrictParserDefault = true;		// Enable strict parsing
+
+or, overridden on a case by case basis:
+
+	Json.Format(person, JsonOption.DontWriteWhitespace);		// Force pretty formatting off
+	Json.Format(person, JsonOption.WriteWhitespace);			// Force pretty formatting on
+	Json.Parse<object>(jsonData, JsonOption.StrictParser);		// Force strict parsing
+	Json.Parse<object>(jsonData, JsonOption.NonStrictParser);	// Disable strict parsing
+
+Non-strict parsing allows the following:
+
+* Inline C /* */ or C++ // style comments
+* Trailing commas in arrays and dictionaries
+* Non-quoted dictionary keys
+
+eg: the non-strict parser will allow this:
+
+{
+	/* This is C-style a comment */
+	"quotedKey": "allowed",
+	nonQuotedKey: "also allowed",
+	"arrayWithTrailingComma": [1,2,3,],	
+	"trailing commas": "allowed ->",	// <- see the comma, not normally allowed
+}
+