diff --git a/CHANGELOG.md b/CHANGELOG.md index 37a1a9e..d3e02e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- The length of objects is now pre-computed, reducing the size of the encoded + object and speeding up the decoing process. Thanks to Karchnu for his contribution. + ## [0.2.1] - 2020-09-29 ### Fixed diff --git a/README.md b/README.md index e745db5..7d4ff48 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ house = House.from_cbor(cbor) house.address # => "Crystal Road 1234" house.location # => # bytes = house.to_cbor # => Bytes[...] -CBOR::Diagnostic.to_s(bytes) # => {_ "address": "Crystal Road 1234", "location": {_ "lat": 12.3, "lng": 34.5}} +CBOR::Diagnostic.to_s(bytes) # => {"address": "Crystal Road 1234", "location": {"lat": 12.3, "lng": 34.5}} data_array = [data] cbor_array = data_array.to_cbor # => Bytes[...] @@ -63,7 +63,7 @@ CBOR::Diagnostic.to_s(cbor) # => [{"address": "Crystal Road 1234", "location houses = Array(House).from_cbor(cbor_array) houses.size # => 1 bytes = houses.to_cbor # => Bytes[...] -CBOR::Diagnostic.to_s(bytes) # => [{_ "address": "Crystal Road 1234", "location": {_ "lat": 12.3, "lng": 34.5}}] +CBOR::Diagnostic.to_s(bytes) # => [{"address": "Crystal Road 1234", "location": {"lat": 12.3, "lng": 34.5}}] ``` ## Installation @@ -158,7 +158,7 @@ end a = A.from_cbor({"a" => 1, "b" => 2}.to_cbor) # => A(@cbor_unmapped={"b" => 2}, @a=1) bytes = a.to_cbor # => Bytes[...] -CBOR::Diagnostic.to_s(bytes) # => {_ "a": 1, "b": 2} +CBOR::Diagnostic.to_s(bytes) # => {"a": 1, "b": 2} ``` ### Class annotation `CBOR::Serializable::Options` diff --git a/spec/cbor/serializable_spec.cr b/spec/cbor/serializable_spec.cr index ff64361..16e2c8b 100644 --- a/spec/cbor/serializable_spec.cr +++ b/spec/cbor/serializable_spec.cr @@ -66,7 +66,7 @@ end describe CBOR::Serializable do describe "rfc examples" do - describe %(example {_ "a": 1, "b": [_ 2, 3]}) do + describe %(example {"a": 1, "b": [2, 3]}) do it "decodes from cbor" do result = ExampleA.from_cbor(Bytes[0xbf, 0x61, 0x61, 0x01, 0x61, 0x62, 0x9f, 0x02, 0x03, 0xff, 0xff]) @@ -75,7 +75,7 @@ describe CBOR::Serializable do end end - describe %(example {_ "Fun": true, "Amt": -2}) do + describe %(example {"Fun": true, "Amt": -2}) do it "decodes from cbor" do result = ExampleB.from_cbor(Bytes[0xbf, 0x63, 0x46, 0x75, 0x6e, 0xf5, 0x63, 0x41, 0x6d, 0x74, 0x21, 0xff]) @@ -84,7 +84,7 @@ describe CBOR::Serializable do end end - describe %(example ["a", {_ "b": "c"}]) do + describe %(example ["a", {"b": "c"}]) do it "decodes from cbor" do result = Array(String | ExampleC).from_cbor(Bytes[0x82, 0x61, 0x61, 0xbf, 0x61, 0x62, 0x61, 0x63, 0xff]) @@ -137,7 +137,7 @@ describe CBOR::Serializable do it "encodes to CBOR" do cbor = House.from_cbor(bytes).to_cbor - CBOR::Diagnostic.to_s(cbor).should eq(%({_ "address": "Crystal Road 1234", "location": {_ "lat": 12.3, "lng": 34.5}})) + CBOR::Diagnostic.to_s(cbor).should eq(%({"address": "Crystal Road 1234", "location": {"lat": 12.3, "lng": 34.5}})) end end @@ -168,7 +168,7 @@ describe CBOR::Serializable do it "encodes to CBOR" do cbor = Array(House).from_cbor(bytes).to_cbor - CBOR::Diagnostic.to_s(cbor).should eq(%([{_ "address": "Crystal Road 1234", "location": {_ "lat": 12.3, "lng": 34.5}}])) + CBOR::Diagnostic.to_s(cbor).should eq(%([{"address": "Crystal Road 1234", "location": {"lat": 12.3, "lng": 34.5}}])) end end @@ -185,7 +185,7 @@ describe CBOR::Serializable do res.a.should eq(1) res.cbor_unmapped.should eq({"b" => 2}) - CBOR::Diagnostic.to_s(res.to_cbor).should eq(%({_ "a": 1, "b": 2})) + CBOR::Diagnostic.to_s(res.to_cbor).should eq(%({"a": 1, "b": 2})) end end end diff --git a/src/cbor/serializable.cr b/src/cbor/serializable.cr index 7ebd7d2..4925470 100644 --- a/src/cbor/serializable.cr +++ b/src/cbor/serializable.cr @@ -243,6 +243,10 @@ module CBOR raise ::CBOR::SerializationError.new("Unknown CBOR attribute: #{key}", self.class.to_s, nil) end + protected def get_cbor_unmapped + {} of String => ::CBOR::Type + end + protected def on_to_cbor(cbor : ::CBOR::Encoder) end @@ -268,9 +272,28 @@ module CBOR {% end %} {% end %} - cbor.object do - {% for name, value in properties %} + # Compute the size of the final list of properties to serialize. + # This allows a more compact encoding, and a faster decoding. + nb_properties_to_serialize = 0 + {% for name, value in properties %} _{{name}} = @{{name}} + {% unless value[:emit_null] %} + unless _{{name}}.nil? + nb_properties_to_serialize += 1 + end + {% else %} + nb_properties_to_serialize += 1 + {% end %} + {% end %} + + nb_properties_to_serialize += get_cbor_unmapped.size + + + {% if properties.size > 0 %} + cbor.write_object_start nb_properties_to_serialize + + {% for name, value in properties %} + _{{name}} = @{{name}} {% unless value[:emit_null] %} unless _{{name}}.nil? @@ -279,22 +302,22 @@ module CBOR # Write the key of the map cbor.write({{value[:key]}}) - {% if value[:converter] %} - if _{{name}} - {{ value[:converter] }}.to_cbor(_{{name}}, cbor) - else - cbor.write(nil, use_undefined: value[:nil_as_undefined]) - end - {% else %} - _{{name}}.to_cbor(cbor) - {% end %} + {% if value[:converter] %} + if _{{name}} + {{ value[:converter] }}.to_cbor(_{{name}}, cbor) + else + cbor.write(nil, use_undefined: value[:nil_as_undefined]) + end + {% else %} + _{{name}}.to_cbor(cbor) + {% end %} {% unless value[:emit_null] %} end {% end %} {% end %} on_to_cbor(cbor) - end + {% end %} {% end %} end @@ -310,6 +333,10 @@ module CBOR end end + protected def get_cbor_unmapped + cbor_unmapped + end + protected def on_to_cbor(cbor : ::CBOR::Encoder) cbor_unmapped.each do |key, value| cbor.write(key)